Model Refinement   «Prev  Next»
Lesson 3 Encapsulation revisited
ObjectiveReinforce the Principle of Encapsulation

Reinforce Encapsulation Principle

Encapsulation, a core principle of object-oriented programming (OOP), is about bundling data (attributes) and the methods (behaviors) that operate on that data into a single unit—typically a class—while restricting direct access to some of that unit’s internal details. It’s like giving a software component a clear interface (e.g., a control panel) while hiding its messy internals (e.g., the wiring). When refining a software model, reinforcing encapsulation ensures your design stays modular, maintainable, and less prone to unintended interference. Here’s how you can do it, with practical steps tailored to refining a model:
  1. Define Clear Public Interfaces
    • Why: Expose only what’s necessary for other parts of the system to interact with the object, keeping the rest hidden.
    • How: Review your model’s classes and identify methods or properties that external code needs to use. Make these public (e.g., getters, setters, or high-level operations) while minimizing their number and scope.
    • Example: If modeling an aircraft system like the 737’s flight control, expose a method like adjustPitch(angle) instead of letting external code directly tweak pitchAngle or stabilizerPosition.
  2. Restrict Access to Internal Data
    • Why: Prevent external code from bypassing intended behavior or breaking invariants by directly modifying state.
    • How: Set attributes to private or protected in your refined model. Use access modifiers (e.g., private in Java/C#, _ in Python) and enforce interaction through methods.
    • Example: In a FlightControl class, make angleOfAttack private and provide a getAngleOfAttack() method instead of letting other classes read or write it directly.
  3. Use Getters and Setters Judiciously
    • Why: They allow controlled access, letting you add validation or side effects without changing the interface.
    • How: When refining, replace direct field access with methods that enforce rules. Avoid blind setters that just assign values—add logic if needed.
    • Example: Instead of public int throttle;, use:
      private int throttle;
      public int getThrottle() { return throttle; }
      public void setThrottle(int value) {
        if (value >= 0 && value <= 100) throttle = value;
        else throw new IllegalArgumentException("Throttle must be 0-100");
      }
              
  4. Hide Implementation Details
    • Why: Shield users from how things work internally, so you can change the guts without breaking dependent code.
    • How: Refactor complex logic into private methods or helper classes. Avoid exposing data structures (e.g., lists, maps) directly—return copies or abstractions instead.
    • Example: If a NavigationSystem class uses a list of waypoints, don’t return List<Waypoint> waypoints. Instead:
      public List<Waypoint> getWaypoints() {
        return new ArrayList<>(waypoints); // Defensive copy
      }
              
  5. Leverage Abstraction
    • Why: Abstract classes or interfaces reinforce encapsulation by defining what a component does, not how.
    • How: In your refined model, extract interfaces for key behaviors. Clients depend on the interface, not the concrete class.
    • Example: Define an IFlightControl interface with adjustPitch() and setAltitude(), then implement it in Boeing737FlightControl. Other systems use IFlightControl, oblivious to the 737-specific details.
  6. Validate and Enforce Invariants
    • Why: Encapsulation isn’t just hiding data—it’s ensuring the object stays in a valid state.
    • How: When refining, add checks in methods that modify state. Centralize this logic within the class.
    • Example: In a FuelSystem class:
      private double fuelLevel;
      public void consumeFuel(double amount) {
        if (amount > fuelLevel) throw new IllegalStateException("Not enough fuel");
        fuelLevel -= amount;
      }
              
  7. Minimize Dependencies
    • Why: Tight coupling undermines encapsulation by leaking implementation concerns across components.
    • How: Refactor your model to inject dependencies (e.g., via constructors) rather than hardcoding them. Use design patterns like Dependency Injection.
    • Example: Instead of FlightControl creating its own Sensor, pass it in:
      public FlightControl(Sensor sensor) {
        this.sensor = sensor;
      }
              
  8. Audit and Refactor Existing Code
    • Why: Refining a model often reveals encapsulation leaks from earlier iterations.
    • How: Look for public fields, overly permissive methods, or external code manipulating internal state. Tighten these up by moving logic inward and restricting access.
    • Example: If Engine has a public rpm field that’s tweaked externally, make it private and add increaseRPM(int delta) with bounds checking.
In a 737’s software model, like for its flight management system—encapsulation ensures that modules (e.g., autopilot, navigation) don’t meddle with each other’s internals. For instance, the MCAS software could encapsulate sensor data processing, only exposing a correctPitch() method. Poor encapsulation (e.g., exposing raw sensor values) might’ve contributed to its real-world issues by allowing unexpected interactions. By reinforcing encapsulation during refinement, you make the model more robust, easier to debug, and adaptable to future changes—like swapping a 737’s sensor suite without rewriting the whole system. Want me to mock up a code example for a specific 737 subsystem?

Domain Driven Design

Specification versus Implementation

The purpose of encapsulation is to separate the specification of an object from its implementation. This separation allows you to use an object without knowing how it is implemented. Because the implementation is hidden, you have the freedom to apply new techniques and technologies to improve the implementation without corrupting the object's purpose and without affecting the other objects that collaborate with it. Unfortunately, we tend to think of objects only as instances of classes in the literal syntax of programming languages. But encapsulation applies to objects at every level of abstraction.
  • Objects Redefined
    An object in the most basic sense is simply an entity. For an analyst, an object could be a unique physical entity, an aggregation of objects, a component, an application, a job, a system, or even a department or a company. Encapsulation is essential at all levels of abstraction in both software and user domains.
    The following series of images contains several examples of objects.

Encapsulation Visual Aggregate Component
1) Purpose: A resource provided to clients for short-term loan
The Book class has the following attributes:
  1. CheckOut(date, duration) – Represents a method to check out a book, requiring a date and a duration.
  2. CheckIn(date) – Represents a method to check in a book, requiring a date.
  3. Stock(location) – Represents a method or attribute that defines where the book is stocked (location).
  4. Catalog(deweydecimalnumber) – Represents a method or attribute related to cataloging the book using the Dewey Decimal System.

These attributes represent the operations or properties relevant to managing books in a library system. The CheckOut and CheckIn methods handle book loans, while Stock and Catalog manage book organization and availability. Object: Book Purpose: A resource provided to clients for short-term loan


2) Purpose: A means of personal transportation
Aggregate: A Car
Purpose: A means of personal transportation


3) Purpose: Provide controlled visual access to the security system
Component: A security user interface
Purpose: Provide controlled visual access to the security system


4) Purpose: Insure that employees are paid according to company policy
Job: Payroll Clerk
Purpose: Insure that employees are paid according to company policy


5) Purpose: Support the creation and alteration of electronic documents.
Application: Word Processing
Purpose: Support the creation and alteration of electronic documents.


6) Purpose: Facilitate and track the payment of all employees in compliance with company policy and state and federal tax laws
System: Payroll System
Purpose: Facilitate and track the payment of all employees in compliance with company policy and state and federal tax laws.


7) Purpose: Coordinate the staff and systems used to insure the proper payment of employees according to company policy and state and  federal laws.
Department: Payroll Department
Purpose: Coordinate the staff and systems used to insure the proper payment of employees according to company policy and state and federal laws.


8) Purpose: Provide expert support for all payroll related services for client companies in conformance with state and federal laws
Company: A Payroll Service
Purpose: Provide expert support for all payroll related services for client companies in conformance with state and federal laws.


Encapsulation reveals Opportunities

When applied consistently to the analysis process, encapsulation often reveals opportunities to redesign not just the software but also the problem domain practices and organization. This opportunity addresses one of the reasons that companies have not received the benefits of object-oriented methods, that is, the fact that the systems development process is not closely linked to the business reengineering process. In fact, you will see in the following lessons that refactoring applies equally well to organizational systems and software systems.

Refactoring Encapsulation - Quiz

Click the Quiz link below to take a short multiple-choice quiz.
Refactoring Encapsulation - Quiz

SEMrush Software