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

Advanced OOP Homework

មូលសង្ខេប / Abstract

this is the summaries of oop lessons from 1 to 8 of class CSM1

ពាក្យគន្លឹះ




Field

Details

Name

Ty Reaksa

Student ID

ST-RE00118

Course/Class

Advanced Object-Oriented Programming

Lecturer

Sek Socheat

University

University of Technology and Entrepreneurship

Coverage

Lesson 1 to 8

Submission Type

Homework


Lesson 1: Advanced OOP Foundation and Setup 


  1. Introduction & Course Context 


This lesson establishes the core foundations of Object-Oriented Programming (OOP) in Python before transitioning into web development using Flask. The primary focus shifts away from basic procedural thinking (step-by-step scripts or lists of dicts) toward object-oriented thinking (creating reusable objects with clear, distinct responsibilities that collaborate). Moving from procedural to advanced OOP makes code significantly easier to maintain, test, and scale into real-world software systems. 


  1. Core OOP Vocabulary and Foundations


  • Class: A blueprint or template used to create objects.

  • Object: A real instance created from a class blueprint.

  • Attribute: Data stored directly inside an object or a class.

  • Method: A function defined inside a class that operates on its data.

  • self: A keyword representing the specific instance executing the method, used to store and access instance-specific data.

  • Responsibility: The specific, single job assigned to a given class.


  1. Python Method Types 


Instance Methods:

  • Take self as the primary parameter.

  • Modify or read individual object state (e.g., user.display()).

Class Methods (@classmethod):

  • Take cls as the primary parameter.

  • Act directly on the class rather than instances.

  • Often serve as Factory Methods to create objects under predefined rules (e.g., User.from_username("dara") generates a user with a default email format).

Static Methods (@staticmethod):

  • Do not receive self or cls.

  • Serve as self-contained helper functions logically placed within a class namespace (e.g., User.is_valid_username(username)).


  1. Mini RBAC Domain Implementation 

To put these concepts into practice, a Role-Based Access Control (RBAC) domain was constructed. The architecture uses a "has-a" composition pattern, where roles group permissions and users hold roles. 

  1. Summary Takeaways

  • Classes act as blueprints, while objects represent individual instances in memory.

  • self binds data specifically to an instance; class attributes share data across all instances.

  • Choose instance, class, or static methods based on whether access to object state (self), class state (cls), or neither is needed.

  • Structuring code into packages and modules improves project organization and scalability.

Lesson 2: Smart Class Design with Data class and Property 

  1. Introduction & Objectives

Building upon the foundations from Lesson 01, Lesson 02 focuses on writing cleaner, safer, and less repetitive Python code. The primary goal is to design classes that make valid object states easy to create and invalid object states hard to create. This lesson introduces @dataclass to auto-generate boilerplate code for data-centric objects and @property decorators to safely control, normalize, and validate object attributes. 

  1. Data-Centric vs. Behavior-Centric Classes 

When designing a class, we must intentionally decide whether it is data-centric or behavior-centric: 

Data-Centric Class: 

Purpose: Mainly stores a structured collection of related values.

Typical Use: Configurations, DTOs (Data Transfer Objects), value objects, or simple database records (e.g., Permission).

Best Tool: @dataclass.

Behavior-Centric Class: 

Purpose: Coordinates state transitions, handles business rules, and manages complex workflows. 

Typical Use: Services, domain roots, or entities with heavy validation (e.g., User). 

Best Tool: A regular Python class with custom methods and properties. 

  1. Standardizing Models with @dataclass 

The @dataclass decorator automatically reads type-annotated fields and generates standard methods that otherwise require repetitive boilerplate code: 

Auto-generated Methods: Generates __init__() (constructor), __repr__() (string representation), and __eq__() (field-by-field equality comparison) by default. 

Field Ordering Rule: Required parameters (without default values) must come before optional parameters (with default values). 

  1. Controlled State With @property

Public fields without protection allow outside code to inject invalid data directly. The @property decorator allows us to keep convenient attribute-style access (e.g., user.email = "...") while running validation and normalization behind the scenes. 

Getter (@property): Provides access to an internal variable (e.g., _email).

Setter (@<property_name>.setter): Intercepts assignments to normalize input (e.g., . strip().lower()), validate business rules, and raise early errors before modifying internal state.

Read-Only & Computed Properties: A property with a getter but no setter becomes read-only. Use this for values that can be dynamically calculated on the fly rather than duplicated in memory (e.g., role_count).

  1. Decision Matrix: Public Field, Property, or Method? 

Interface Type

When to use

Example

Public Field 

Direct data access where invalid values pose no risk. 

permission.description

Property 

Attributes requiring normalization, validation, or live calculation. 

user.email, user.role_count

Method 

Actions that perform work, take extra parameters, or alter multiple internal states. 

user.assign_role(role)


  1. Summary Takeaway

@dataclass removes repetitive boilerplate for classes that primarily hold data, but shouldn't replace regular classes where behavior dominates.

Derive calculated values (like permission_count) using properties instead of keeping redundant instance variables that can easily go out of sync.


Lesson 3: Python Object Model and Special Methods 

  1. Introduction and Objectives

Lesson 03 moves from class quality to making custom objects participate naturally in standard Python syntax. Rather than inventing arbitrary method names, we leverage Python's special methods (also known as dunder methods, beginning and ending with double underscores __) to hook into language protocols. The core design question is: How can a class communicate its domain meaning clearly to both Python and other developers? 

  1. Core Python Object Model Foundations 

Every Python object consists of three fundamental properties: 

  • Identity: The unique memory address of the object. Observed using id(obj) and compared using the is operator. 

  • Type: Defines what operations and behavior the object supports. Observed using type(obj) or isinstance(obj, Class). 

  • Value: The state and information the object represents. Tested for domain equality using the == operator. 

  • is checks whether two variable names point to the exact same object in memory (identity). 

  • == checks whether two distinct objects hold equal content (value equality), as controlled by __eq__(). 

Attribute Lookup Order 

When evaluating obj.attribute, Python searches in order: 

  • Instance namespace: Look inside self.__dict__. 

  • Class namespace: Look for class attributes or methods. 

  • Base classes: Search through the Method Resolution Order (MRO). 

  • Raise AttributeError if lookup fails. 


  1. String Representations: __str__() vs. __repr__() 

Both return string representations, but they serve different audiences: 

__str__(): Intended for human/application users. Should be clean, user-friendly, and readable (e.g., dara). Called by str(obj) and print(obj). 

__repr__(): Intended for developers and debuggers. Should be precise, detailed, and unambiguous (e.g., User(user_id=1, username="dara")). Called by repr(obj) and when inspecting objects in lists/containers. 

  1. Container Protocols

By implementing standard container methods, custom collection classes can wrap storage while offering natural list- or dict-like access: 

__len__(): Returns the count of items in the container (len(obj)). 

__iter__(): Delegates iteration to internal storage (iter(self._users)), enabling for users in directory: without exposing internal list modification. 

__contains__(): Enables membership tests via x in obj. Can support multiple types (e.g., checking by object, ID integer, or username string). 

__getitem__(): Handles positional indexing (directory[0]), slicing (directory[:2]), or domain key lookups (directory["dara"]). Raises KeyError for missing key strings or TypeError for bad key types.

  1. Operator Overloading & Context Managers 

    1. Operator Overloading (__or__()) 

Operators should only be overloaded when their meaning is obvious, natural, and non-destructive. Example: Overloading | on Role objects performs a set union of permissions and returns a new Role instance without mutating the original operands. 

  1. Context Managers (__enter__() and __exit__()) 

Context managers control resource lifecycles using blocks, guaranteeing execution of setup and teardown routines: __enter__(): Initializes or acquires resources, writes initial setup state, and returns the context target (bound to as). __exit__(exc_type, exc_value, traceback): Executes teardown tasks (e.g., closing files, logging success/errors). Returning False allows exceptions raised inside the block to propagate, while returning True suppresses them. 

  1. Summary Takeaways 

  • Special methods map Python syntax (like print, len, in, [], |, with) directly to class protocols.

  • Make __repr__() detailed for developers and __str__() concise for end users.

  • __eq__() must return NotImplemented for incompatible types, and __hash__() must only use immutable identity attributes.

  • Delegate container operations like __iter__() to internal data collections to preserve encapsulation.

  • Operator overloading should return new instances rather than mutating existing ones.

  • Context managers (__enter__ / __exit__) provide reliable cleanup and audit capabilities regardless of whether code succeeds or throws an exception.

Lesson 4: Abstraction, Composition, Mixins, and Protocols 

  1. Introduction & Objectives 

Lesson 04 shifts focus from individual object behavior to relationships between objects. The core question addressed is: How can objects cooperate without knowing too much about one another? Rather than forcing every class into a deep, rigid inheritance hierarchy, good design uses distinct mechanisms depending on the true relationship between entities. 

  1. Four Lenses for Object Relationships 

Choosing the right tool begins with identifying the underlying domain relationship: 

Abstract Base Class (ABC): Nominal is-a: Defines a controlled family of classes where required behaviors are explicitly enforced.

Composition Has-a / Uses-a: Connects focused objects that cooperate to complete work without tightly coupling them.

Mixin Behavior reuse: Shares small, optional capabilities across unrelated classes without defining domain identity.

Protocol Behaves-like: Defines a structural contract where compatibility is based on matching methods/properties, regardless of class inheritance.

  1. Abstract Base Classes (ABCs) 

ABCs define a mandatory nominal contract using Python's abc module (ABC and @abstractmethod). 

Enforcement: Python prevents instantiation of any incomplete subclass that fails to implement abstract methods or abstract properties. 

Shared Defaults: An ABC can combine abstract requirements with concrete helper methods (such as input validation). 

  1. Composition over Inheritance 

Inheritance introduces strong coupling because base class changes cascade to all subclasses. Composition assembles small, focused collaborators and delegates work to them. 

Constructor Injection: By passing dependencies into the constructor rather than creating them internally, dependencies become explicit. This makes replacing collaborators or passing test fakes trivial. 

  1. Mixins and Method Resolution Order (MRO)

A Mixin provides a single, narrow capability (e.g., retries, logging, or serialization) without representing a main business identity. 

Design Rules: Mixins should remain small, avoid defining complex states (__init__), and be aware of inheritance order. 

MRO & super(): Python resolves multiple inheritance using Method Resolution Order (MRO). Mixins must be listed before the base class in class definitions to ensure their method overrides execution first.

  1. Summary Takeaways  

Expose small, stable contracts while keeping changing internal implementation details hidden.

  • Use Abstract Base Classes (ABCs) when you own a family of classes and want to enforce mandatory concrete implementations at instantiation time.

  • Prefer composition over inheritance to build systems out of replaceable, easily testable collaborators.

  • Keep mixins focused on a single capability, list them first in inheritance, and rely on cooperative super() calls.

  • Use Protocol structural typing to accept compatible objects from third-party libraries or test doubles without forcing shared inheritance.

  • Combine these patterns in practice: ABCs for internal family rules, composed service coordinators, mixins for selective capabilities, and Protocols for consumer boundaries

Lesson 5: Factory Method and Abstract Factory 

  1. Introduction and Objectives

Chapter 05 focuses on separating object creation from object usage. When client code instantiates concrete classes directly throughout an application, changes become risky, code becomes tightly coupled, and testing grows difficult. The central design goal of this chapter is to delegate creation decisions to dedicated components so the rest of the application can interact with objects through stable abstract contracts. 

  1. The Object Creation Problem 

Directly instantiating objects inside business logic creates three main architectural problems: 

  • High Coupling: Functions must know every concrete class and its specific initialization requirements.

  • Repeated Modification: Adding a new class variant requires editing conditional selection blocks across multiple files.

  • Testing Friction: Mixing object instantiation with core business logic makes it difficult to mock or replace dependencies during testing.


  1. Simple Factory & Class-Based Selection 

A Simple Factory centralizes object creation into a single function or class method. Instead of scattering conditional logic, the client requests an object by passing a parameter, and the factory evaluates the input to return the correct instance.

Using a class with static creation methods helps organize related helper logic and makes the creation role explicit in the codebase. This pattern works best when selecting among a small set of known options, though long conditional blocks can still become a maintenance bottleneck if options grow significantly.

  1. Factory Method Pattern 

The Factory Method pattern delegates product creation to subclasses while keeping the execution workflow in a base class. 

  • Template Workflow: An abstract base creator defines a common execution method that calls a factory method to obtain a product.

  • Subclass Decision: Concrete subclasses override the factory method to decide which specific product instance to instantiate.

  • Class Method Factories: Python also supports alternative constructors using class methods, providing descriptive, named entry points to create objects from varied input formats like raw strings or dictionary payloads.


  1. Registry-Based Factory 

To avoid hardcoded conditional chains and comply with the Open-Closed Principle, a Registry Factory uses a mapping of keys to class types.

New product classes register themselves with the factory at runtime. When a client requests an instance, the factory looks up the key in its registry map and instantiates the corresponding class. This approach allows developers to introduce new product types without modifying existing factory lookup logic.

  1. Abstract Factory Pattern 

When an application requires whole families of distinct but interdependent products, the Abstract Factory pattern ensures that all created products belong to the same matching theme or environment. 

Family Consistency: An abstract factory interface declares creation methods for each product type in a family.

Concrete Implementations: Concrete factory classes implement these methods to produce matching product sets, such as cohesive user interface components or consistent platform-specific services.

Client Isolation: The application receives a factory instance through dependency injection and creates required UI or system elements without needing to know their underlying concrete classes.

  1. Practical Role-Based Access Control (RBAC) & Dependency Injection 

Factories simplify complex default configurations, such as constructing user accounts with pre-configured role levels and permissions. Hiding setup details inside a factory prevents business logic from repeating administrative setup code.

When paired with Dependency Injection, factories handle selecting and instantiating the correct concrete implementation, while business services simply receive the completed object and interact with it through its interface contract.

  1. Summary Takeaways 

  • Separating object creation from usage allows applications to depend on flexible abstractions rather than rigid concrete classes.

  • Simple factories gather creation decisions into single entry points to eliminate duplicated instantiation logic.

  • Factory Methods allow base workflows to stay fixed while letting subclasses control product instantiation.

  • Registry factories make systems easily extensible by supporting product registration without modifying core creation code.

  • Abstract Factories ensure that groups of related components always remain compatible and consistent.

Lesson 6: Decorators and Cross-Cutting Concerns 

  1. Introduction & Objectives 

Chapter 06 introduces decorators as a mechanism to attach reusable behavior around core function logic without altering the function's internal implementation. In software systems, cross-cutting concerns such as logging, timing, input validation, authentication, and permission checks frequently clutter core business methods. By moving these secondary responsibilities into standalone decorators, code becomes cleaner, easier to test, and more maintainable. 

  1. Core Python Foundations 

Decorators rely on fundamental features of Python's functional programming design: 

First-Class Functions: Functions can be stored in variables, passed into other functions as arguments, and returned from functions like any other object.

Higher-Order Functions: Functions that accept other functions as parameters or output new functions.

Closures: Inner wrapper functions retain access to variables defined in their outer enclosing scope even after the outer function finishes executing.

  1. Decorator Architecture & Best Practices 

A decorator takes a target function as input, constructs an internal wrapper function, and returns that wrapper. To write robust, production-ready decorators, three core guidelines must be followed: 

Flexibility: Use variable positional and keyword argument forwarding so the wrapper can accept any parameters required by the target function.

Return Value Retention: Ensure the wrapper explicitly returns the execution result of the original function rather than accidentally swallowing return values.

Metadata Preservation: Apply metadata preservation helpers to copy the original function's name, docstrings, and annotations onto the returned wrapper, keeping debugging and testing tools accurate.

  1. Parameterized Decorators 

When a decorator requires external configuration such as checking for a specific permission code it requires a three-layer factory structure: 

Outer Factory Layer: Accepts and stores configuration parameters.

Middle Decorator Layer: Accepts the target function.

Inner Wrapper Layer: Accesses both the outer configuration values and the target function's parameters at runtime to execute pre- and post-processing logic.

  1. Decorator Stacking & Class Registration 

When multiple decorators are applied to a single function, they form an execution pipeline where outer decorators execute first, wrapping the decorators underneath them. The top decorator receives the result of the decorator directly below it. 

Beyond wrapping functions, decorators can also accept classes as inputs. A class registration decorator can automatically record newly defined classes into a registry dictionary, making it seamless to register product implementations for factory lookups. 

  1. Summary Takeaways 

  • Decorators allow cross-cutting behavior to wrap core business logic without diluting the primary purpose of business functions. 

  • First-class functions and closures provide the underlying mechanism for decorators to intercept function calls and store state. 

  • Well-crafted decorators forward all positional and keyword parameters, preserve return values, and maintain function metadata. 

  • Parameterized decorators use factory functions to pass dynamic configuration values into execution wrappers. 

  • Stacking decorators creates a structured pipeline, executing from the outermost wrapper down to the core target function. 

  • Class decorators can bridge into factory patterns by automatically registering concrete implementations upon class definition. 


Lesson 7: SOLID Principles and Refactoring 

  1. Introduction & Objectives 

Lesson 07 focuses on applying the SOLID principles and systematic refactoring to build maintainable, extensible, and testable object-oriented software. As applications grow, unguided code can easily become overly complex, tightly coupled, and difficult to test. The primary goal of this chapter is to teach developers how to identify common design flaws, break large classes into focused components, and use abstractions and dependency injection to insulate business logic from infrastructure changes.

  1. Code Smells & The Need for Refactoring 

Refactoring is the process of improving internal code structure without altering external behavior. Unhealthy code typically displays warning signs known as code smells: 

Monolithic Classes & Methods: Single entities that handle far too many tasks, such as combining database access, input validation, and user notification in one place. 

Tight Coupling & Hidden Dependencies: Classes that construct their own hardcoded dependencies internally, making component replacement or isolated unit testing impossible. 

Rigid Conditionals: Excessive conditional chains where adding a single new feature requires editing existing control flow. 

Unexpected Subclass Behavior: Subclasses that override parent class methods in ways that break caller expectations or throw unexpected support errors. 

  1. Single Responsibility Principle (SRP) 

When a single manager class attempts to validate input, hash passwords, execute database queries, transmit emails, and record audit logs, any change to database schemas or email providers forces edits to that monolithic class. Refactoring under SRP splits these concerns into specialized helper objects such as validators, password hashers, repositories, and notification channels, while a higher-level coordinator service manages the execution workflow. 

  1. Open/Closed Principle (OCP) 

Software entities should be open for extension but closed for modification. Relying on hardcoded conditional checks to route actions to specific providers requires modifying core functions every time a new provider is added. By depending on a stable abstract contract instead of concrete implementations, developers can introduce new features simply by creating new classes that implement the contract, extending system behavior without touching or risking existing code. 

  1. Liskov Substitution Principle (LSP) 

The Liskov Substitution Principle requires that subtypes must be completely substitutable for their base types without altering program correctness or breaking expectations. If a subclass overrides a base method by raising an error that the feature is unsupported, it violates substitution expectations. Callers relying on the base contract should be able to execute methods on any valid subclass without needing special checks or handling unexpected runtime failures. 

  1. Interface Segregation Principle (ISP) 

Instead of creating massive, monolithic protocols or interfaces that bundle unrelated operations like payments, database backups, and email delivery, ISP encourages designing smaller, tightly focused contracts. Classes then depend only on the narrow set of behaviors necessary to perform their specific job. 

  1. Dependency Inversion Principle (DIP) & Testing Benefits 

High-level business logic should depend on abstractions rather than concrete lower-level modules or infrastructure details. 

Constructor Injection: Rather than instantiating database handlers directly inside a service, the service receives an abstract contract during initialization. 

Testability via Fake Dependencies: By decoupling business rules from real databases or external APIs, unit tests can inject lightweight fake objects into services. This allows tests to verify error paths, validation rules, and business logic quickly and safely without needing external infrastructure running. 

  1. Summary Takeaways 

Applying SOLID principles ensures that each class maintains a single reason to change while allowing new features to be added through extension rather than risky edits to existing functions. Derived subclasses can safely replace parent abstractions without breaking caller logic, and small, focused contracts prevent clients from depending on unused methods. Furthermore, by using dependency inversion to decouple business logic from external infrastructure, refactoring produces code that is significantly easier to read, test with fakes, and maintain safely over time. 

Lesson 8: Strategy, Template Method, and Command Patterns 

  1. Introduction & Objectives 

Lesson 08 covers behavioral design patterns, focusing on how objects interact and distribute responsibility to handle changing business logic and workflow execution. Instead of relying on hardcoded logic or complex conditional branches, behavioral patterns organize dynamic behavior so core services remain stable and easy to test. The primary objective is to understand, implement, and compare three fundamental behavioral patterns: Strategy, Template Method, and Command. 

  1. Strategy Pattern 

The Strategy pattern defines a family of algorithms, encapsulates each one behind a common contract, and makes them interchangeable at runtime. Instead of using monolithic conditional blocks to evaluate choices such as calculated checkout discounts or payment methods the context service delegates the calculation to a strategy object. Because the context interacts strictly through an abstract strategy interface, developers can introduce new algorithms or swap policies dynamically without modifying the service logic. 

  1. Template Method Pattern 

An abstract base class defines the top-level template method containing the required workflow steps, such as loading, validating, formatting, and saving data. Common logic is kept inside the base class, whereas variable steps are left abstract or provided as optional hooks. Subclasses then override these specialized steps such as exporting data to CSV versus JSON without altering the overall execution order. 

  1. Comparing Strategy and Template Method 

While both patterns vary behavior, they rely on different core object-oriented mechanisms: 

Strategy Pattern: Uses object composition. The context holds a reference to a strategy object, allowing algorithm selection to change dynamically at runtime. It addresses the question of which algorithm to execute. 

Template Method Pattern: Uses inheritance. The base class retains control over the skeleton of the workflow, relying on child subclasses to implement specific steps. It addresses the question of which steps within a process vary. 

  1. Command History & Undo Capabilities 

A command manager maintains an execution history stack. When executing a command, the manager pushes the object onto its history. To perform an undo operation, the manager pops the last executed command from the stack and invokes its undo method, restoring the application state safely using stored context details. 

  1. Integrated Architectural Overview 

In complex applications, these behavioral patterns work together across different architectural layers: 

Commands: Encapsulate user-driven requests and task actions coming from the client UI.

Strategies: Manage dynamic business policies and algorithms inside business services.

Template Methods: Standardize complex, multi-step system processing workflows such as reporting or data ingestion.

  1. Summary Takeaways 

Choosing the right factory structure depends on specific system requirements. A Simple Factory centralizes direct conditional selection for small sets of products, whereas a Factory Method lets subclasses choose which product object to build inside a shared base workflow. A Registry Factory uses key-to-class mappings to support dynamic extension without changing lookup code, while an Abstract Factory enforces consistency across whole families of related products. Factories should be used when object creation involves runtime selection, complex initial setup, or tight coupling risks, whereas straightforward and uniform constructions do not require adding factory overhead.


TYREAKSA_SumHW.pdf
PDF ឯកសារ
ទាញយក អាន
ចែករំលែក
រក្សាទុក
ការផ្សព្វផ្សាយ
ការផ្សព្វផ្សាយ
មតិយោបល់ 0
ចូលគណនី ដើម្បីបញ្ចេញមតិ