Overview
Information hiding separates the public interface of a software module from its internal implementation. The principle dictates that modules should expose only what other parts of the system need to interact with them, while keeping implementation details private and subject to change without affecting external code.
David Parnas introduced information hiding in his 1972 paper "On the Criteria To Be Used in Decomposing Systems into Modules." The principle addresses the problem of software maintenance and evolution by creating boundaries between modules based on design decisions that are likely to change.
The core idea involves two distinct concepts that work together: abstraction and encapsulation. Abstraction defines what a module does by specifying its public interface. Encapsulation hides how the module accomplishes its tasks by restricting access to internal state and implementation details.
Information hiding reduces coupling between modules. When Module A uses Module B, Module A depends only on B's public interface, not its internal workings. Changes to B's implementation do not require changes to A, as long as the interface remains stable.
# Public interface exposes what the module does
class BankAccount
def initialize(balance)
@balance = balance
end
def deposit(amount)
@balance += amount
end
def balance
@balance
end
end
# Client code depends only on public interface
account = BankAccount.new(1000)
account.deposit(500)
puts account.balance # => 1500
The principle applies at multiple levels: within classes through method visibility, across classes through interface design, and across modules or packages through public APIs. Each level creates a boundary that protects implementation details from external access.
Key Principles
Information hiding rests on three foundational principles: interface segregation, implementation independence, and access control.
Interface Segregation means exposing the minimal interface necessary for clients to accomplish their tasks. A well-designed interface includes only operations that external code legitimately needs. Every public method represents a commitment to maintain that interface across future versions, so expanding interfaces should be done deliberately.
The interface defines a contract between the module and its clients. This contract specifies what operations are available, what parameters they accept, and what results they produce. The contract says nothing about how operations are implemented internally.
Implementation Independence allows internal implementation to change without affecting external code. A module can modify algorithms, data structures, and internal state management freely, as long as the public interface behavior remains consistent.
Consider a cache implementation. The interface might offer get and set operations. Internally, the cache could use a hash table, an LRU list, a tree structure, or any other mechanism. Clients neither know nor care about the internal structure because they interact exclusively through the interface.
class Cache
def initialize
@storage = {} # Could be replaced with any structure
end
def get(key)
@storage[key]
end
def set(key, value)
@storage[key] = value
end
end
Access Control enforces information hiding through language mechanisms that restrict which code can access which parts of a module. Without access control, developers could bypass interfaces and directly manipulate internal state, creating dependencies on implementation details.
Access control operates at compile time or runtime. Static languages typically enforce access control at compile time, preventing code from compiling if it violates access restrictions. Dynamic languages like Ruby enforce access control at runtime, raising errors when code attempts to call restricted methods.
The principle of least privilege applies here: code should have access only to the minimum information and operations it needs. If a method does not need to modify internal state, it should not have access to do so. If external code does not need to call a method, that method should remain private.
Information hiding creates levels of abstraction. Each level exposes an interface to the level above while hiding implementation details. The level below can change its implementation without affecting higher levels, as long as interfaces remain stable.
The principle also enforces the Single Responsibility Principle. When implementation details hide behind interfaces, each module has a clear, focused purpose defined by its public interface. Internal complexity does not leak out to confuse the module's role in the larger system.
Ruby Implementation
Ruby provides three visibility levels for methods: public, private, and protected. These keywords control which code can invoke methods, creating access boundaries within classes.
Public Methods form the class's external interface. Any code with a reference to an object can call its public methods. Public methods define what the class does from an external perspective.
class Document
def initialize(content)
@content = content
end
# Public method - external interface
def word_count
count_words(@content)
end
private
# Private method - internal implementation
def count_words(text)
text.split.length
end
end
doc = Document.new("Hello world from Ruby")
doc.word_count # => 4
doc.count_words("test") # NoMethodError: private method called
Private Methods cannot be called with an explicit receiver, even within the same class. This restriction enforces information hiding by preventing both external code and subclasses from depending on private implementation details. Private methods represent internal implementation that could change at any time.
Prior to Ruby 2.7, private methods could not be called with self as the receiver. Ruby 2.7 changed this to allow self.private_method calls, which helps when private methods need to be called from within blocks or when clarity requires explicit receivers.
class Account
def initialize(balance)
@balance = balance
@transaction_log = []
end
def transfer(amount, recipient)
return false unless validate_transfer(amount)
deduct(amount)
recipient.deposit(amount)
log_transaction("transfer", amount, recipient)
true
end
private
def validate_transfer(amount)
amount > 0 && @balance >= amount
end
def deduct(amount)
@balance -= amount
end
def log_transaction(type, amount, recipient)
@transaction_log << {
type: type,
amount: amount,
recipient: recipient.object_id,
timestamp: Time.now
}
end
end
Protected Methods occupy a middle ground. Protected methods can be called by any instance of the same class or its subclasses, but not by external code. This visibility level supports scenarios where related objects need to access each other's internal state.
class Employee
attr_reader :name
def initialize(name, salary)
@name = name
@salary = salary
end
def salary_higher_than?(other_employee)
salary > other_employee.salary
end
protected
attr_reader :salary
end
alice = Employee.new("Alice", 75000)
bob = Employee.new("Bob", 80000)
alice.salary_higher_than?(bob) # => false
alice.salary # NoMethodError: protected method called
Instance Variables hide automatically in Ruby. No external code can directly access instance variables without explicitly defined accessor methods. This default hiding enforces encapsulation at the data level.
class Configuration
def initialize
@settings = {}
@loaded = false
end
def get(key)
load_if_needed
@settings[key]
end
def set(key, value)
load_if_needed
validate_key(key)
@settings[key] = value
end
private
def load_if_needed
return if @loaded
# Load configuration from file or environment
@settings = load_from_source
@loaded = true
end
def validate_key(key)
raise ArgumentError, "Invalid key" unless key.is_a?(String)
end
def load_from_source
# Implementation detail
{}
end
end
Module Encapsulation creates boundaries at a higher level. Ruby modules can group related classes and define module-level private methods that remain hidden from external code.
module PaymentProcessing
class Transaction
def initialize(amount)
@amount = amount
@id = PaymentProcessing.generate_transaction_id
end
def process
return false unless PaymentProcessing.validate_amount(@amount)
# Process transaction
true
end
end
class << self
def generate_transaction_id
# Internal implementation
"TXN-#{Time.now.to_i}-#{rand(10000)}"
end
def validate_amount(amount)
amount.is_a?(Numeric) && amount > 0
end
private :generate_transaction_id, :validate_amount
end
end
Accessor Methods control how external code accesses internal state. Ruby provides attr_reader, attr_writer, and attr_accessor to generate accessor methods, but custom accessors offer more control.
class Temperature
def initialize(celsius)
@celsius = celsius
end
# Controlled read access with computed value
def fahrenheit
@celsius * 9.0 / 5.0 + 32
end
# Controlled write access with validation
def celsius=(value)
raise ArgumentError, "Invalid temperature" if value < -273.15
@celsius = value
end
def celsius
@celsius
end
end
Class Instance Variables hide information at the class level. Unlike class variables, class instance variables do not share across inheritance hierarchies, providing better encapsulation for class-level state.
class Counter
class << self
attr_reader :count
def initialize_counter
@count = 0
end
def increment
@count += 1
end
private :initialize_counter
end
initialize_counter
end
Practical Examples
Information hiding appears in scenarios ranging from simple data validation to complex system architectures. These examples demonstrate how the principle applies across different problem domains.
Database Connection Management hides connection pooling, query optimization, and connection lifecycle from application code.
class DatabaseManager
def initialize(config)
@config = config
@connection_pool = []
@max_connections = config[:max_connections] || 5
initialize_pool
end
def execute_query(sql, params = [])
connection = acquire_connection
begin
result = connection.execute(sql, params)
process_result(result)
ensure
release_connection(connection)
end
end
def transaction(&block)
connection = acquire_connection
begin
connection.begin_transaction
result = block.call(connection)
connection.commit
result
rescue => e
connection.rollback
raise e
ensure
release_connection(connection)
end
end
private
def initialize_pool
@max_connections.times do
@connection_pool << create_connection
end
end
def create_connection
# Connection creation implementation
Connection.new(@config)
end
def acquire_connection
# Connection pool management logic
@connection_pool.pop || create_connection
end
def release_connection(connection)
@connection_pool.push(connection) if @connection_pool.size < @max_connections
end
def process_result(result)
# Result processing and transformation
result.map(&:to_h)
end
end
Authentication System hides password hashing, token generation, and session management details from application controllers.
class AuthenticationService
def initialize
@sessions = {}
@failed_attempts = {}
end
def authenticate(username, password)
return nil if account_locked?(username)
user = find_user(username)
return handle_failed_attempt(username) unless user
return handle_failed_attempt(username) unless verify_password(user, password)
clear_failed_attempts(username)
create_session(user)
end
def validate_session(token)
session = @sessions[token]
return nil unless session
return nil if session_expired?(session)
refresh_session(token)
session[:user]
end
def logout(token)
@sessions.delete(token)
end
private
def find_user(username)
# User lookup implementation
User.find_by(username: username)
end
def verify_password(user, password)
BCrypt::Password.new(user.password_hash) == password
end
def create_session(user)
token = generate_secure_token
@sessions[token] = {
user: user,
created_at: Time.now,
last_accessed: Time.now
}
token
end
def generate_secure_token
SecureRandom.hex(32)
end
def session_expired?(session)
Time.now - session[:last_accessed] > 3600
end
def refresh_session(token)
@sessions[token][:last_accessed] = Time.now
end
def account_locked?(username)
attempts = @failed_attempts[username]
attempts && attempts[:count] >= 5 &&
Time.now - attempts[:last_attempt] < 900
end
def handle_failed_attempt(username)
@failed_attempts[username] ||= { count: 0, last_attempt: Time.now }
@failed_attempts[username][:count] += 1
@failed_attempts[username][:last_attempt] = Time.now
nil
end
def clear_failed_attempts(username)
@failed_attempts.delete(username)
end
end
File Processing Pipeline hides format detection, encoding handling, and transformation logic from client code.
class FileProcessor
def initialize
@processors = register_processors
end
def process_file(filepath)
content = read_file(filepath)
format = detect_format(filepath, content)
processor = select_processor(format)
processor.process(content)
end
private
def read_file(filepath)
encoding = detect_encoding(filepath)
File.read(filepath, encoding: encoding)
end
def detect_encoding(filepath)
sample = File.read(filepath, 1024, mode: 'rb')
return 'UTF-8' if sample.force_encoding('UTF-8').valid_encoding?
return 'ISO-8859-1' if sample.force_encoding('ISO-8859-1').valid_encoding?
'ASCII-8BIT'
end
def detect_format(filepath, content)
return :json if filepath.end_with?('.json')
return :xml if filepath.end_with?('.xml')
return :csv if filepath.end_with?('.csv')
detect_format_from_content(content)
end
def detect_format_from_content(content)
return :json if content.strip.start_with?('{', '[')
return :xml if content.strip.start_with?('<')
:text
end
def select_processor(format)
@processors[format] || @processors[:text]
end
def register_processors
{
json: JSONProcessor.new,
xml: XMLProcessor.new,
csv: CSVProcessor.new,
text: TextProcessor.new
}
end
end
Cache Implementation hides eviction policies, storage mechanisms, and cache warming strategies.
class LRUCache
def initialize(capacity)
@capacity = capacity
@cache = {}
@access_order = []
end
def get(key)
return nil unless @cache.key?(key)
update_access_order(key)
@cache[key]
end
def set(key, value)
if @cache.key?(key)
@cache[key] = value
update_access_order(key)
else
evict_if_needed
@cache[key] = value
@access_order.push(key)
end
end
def size
@cache.size
end
private
def update_access_order(key)
@access_order.delete(key)
@access_order.push(key)
end
def evict_if_needed
return if @cache.size < @capacity
lru_key = @access_order.shift
@cache.delete(lru_key)
end
end
Design Considerations
Information hiding requires careful interface design. The public interface determines how flexible the implementation can be while maintaining compatibility with client code.
Interface Stability versus flexibility represents the central trade-off. Stable interfaces minimize breaking changes but constrain implementation options. Flexible interfaces accommodate evolution but require more frequent client updates.
Design interfaces based on what operations clients need to perform, not based on current implementation details. An interface designed around implementation tends to expose methods that make sense for the internal data structures but not for client needs.
# Implementation-oriented interface (problematic)
class UserRegistry
def get_hash_table
@users
end
def set_hash_table(table)
@users = table
end
end
# Task-oriented interface (better)
class UserRegistry
def find_user(id)
@users[id]
end
def register_user(user)
@users[user.id] = user
end
def all_users
@users.values
end
end
Granularity affects both usability and flexibility. Fine-grained interfaces expose many small methods, each doing one specific thing. Coarse-grained interfaces expose fewer methods that do more work. Fine-grained interfaces offer more flexibility but require more method calls to accomplish tasks. Coarse-grained interfaces simplify client code but may not fit all use cases.
The appropriate granularity depends on the module's role. Low-level utilities benefit from fine-grained interfaces that compose into complex operations. High-level services benefit from coarse-grained interfaces that encapsulate common workflows.
Information Leakage occurs when implementation details inadvertently appear in the public interface. Common sources include returning internal data structures, accepting internal representations as parameters, or exposing error conditions tied to implementation choices.
# Leaks implementation (array internals exposed)
class TaskQueue
def tasks
@tasks # Returns internal array - clients could modify it
end
end
# Hides implementation
class TaskQueue
def tasks
@tasks.dup # Returns copy, protects internal state
end
def each_task(&block)
@tasks.each(&block) # Controlled iteration
end
end
Versioning Strategies help evolve interfaces without breaking client code. Add new methods while maintaining old ones, mark deprecated methods clearly, and provide migration paths when removing functionality.
class PaymentGateway
def process_payment(amount, card_number)
warn "process_payment is deprecated, use process_payment_secure instead"
process_payment_secure(amount, card_number, {})
end
def process_payment_secure(amount, card_number, options = {})
# New implementation with additional security options
end
end
Dependency Injection supports information hiding by allowing implementations to be substituted without changing client code. Rather than creating dependencies internally, modules accept them through constructors or setters.
class OrderProcessor
def initialize(payment_gateway, inventory_service, notification_service)
@payment_gateway = payment_gateway
@inventory_service = inventory_service
@notification_service = notification_service
end
def process_order(order)
return false unless @inventory_service.reserve_items(order.items)
payment_result = @payment_gateway.charge(order.amount, order.payment_info)
return false unless payment_result.success?
@notification_service.send_confirmation(order.customer_email)
true
end
end
Interface Segregation Principle suggests splitting large interfaces into smaller, focused interfaces. Clients should not depend on methods they do not use. Large interfaces couple clients to functionality they do not need, making changes more difficult.
Law of Demeter guides information hiding at the method call level. A method should only call methods on objects it directly knows about: its parameters, objects it creates, its instance variables, and globally available objects. Following this law prevents reaching through objects to access their internal structure.
Common Patterns
Several established patterns implement information hiding across different scenarios. Each pattern addresses specific needs while maintaining encapsulation boundaries.
Facade Pattern provides a simplified interface to a complex subsystem. The facade hides the subsystem's internal complexity behind a cohesive interface tailored to common use cases.
module MediaLibrary
class Facade
def initialize
@file_reader = FileReader.new
@decoder = Decoder.new
@audio_mixer = AudioMixer.new
@video_processor = VideoProcessor.new
end
def play_media(filepath)
data = @file_reader.read(filepath)
format = @file_reader.detect_format(filepath)
if audio_format?(format)
play_audio(data, format)
elsif video_format?(format)
play_video(data, format)
else
raise "Unsupported format: #{format}"
end
end
private
def play_audio(data, format)
decoded = @decoder.decode_audio(data, format)
@audio_mixer.play(decoded)
end
def play_video(data, format)
decoded = @decoder.decode_video(data, format)
audio_track = @decoder.extract_audio(decoded)
video_track = @decoder.extract_video(decoded)
@audio_mixer.play(audio_track)
@video_processor.display(video_track)
end
def audio_format?(format)
[:mp3, :wav, :flac].include?(format)
end
def video_format?(format)
[:mp4, :avi, :mkv].include?(format)
end
end
end
Strategy Pattern hides algorithm implementation behind a common interface. Clients work with strategies through the interface without knowing implementation details.
class DataExporter
def initialize(strategy)
@strategy = strategy
end
def export(data)
@strategy.export(data)
end
end
class JSONExportStrategy
def export(data)
require 'json'
JSON.generate(data)
end
end
class XMLExportStrategy
def export(data)
build_xml_document(data)
end
private
def build_xml_document(data)
# XML building implementation
end
end
class CSVExportStrategy
def export(data)
require 'csv'
CSV.generate do |csv|
data.each { |row| csv << row }
end
end
end
Template Method Pattern hides variation points in an algorithm while exposing the overall structure. Subclasses override specific steps without changing the algorithm's framework.
class ReportGenerator
def generate_report(data)
prepare_data(data)
formatted = format_report(data)
add_header(formatted)
add_footer(formatted)
finalize_report(formatted)
end
private
def prepare_data(data)
# Common preparation logic
end
def format_report(data)
raise NotImplementedError, "Subclasses must implement format_report"
end
def add_header(formatted)
# Optional hook - default implementation
end
def add_footer(formatted)
# Optional hook - default implementation
end
def finalize_report(formatted)
formatted
end
end
class PDFReportGenerator < ReportGenerator
private
def format_report(data)
# PDF-specific formatting
end
def add_header(formatted)
# Add PDF header
end
end
Adapter Pattern hides incompatible interfaces behind a consistent interface. The adapter translates between the interface clients expect and the interface the adapted class provides.
class ModernPaymentGateway
def process(amount, token)
# New API implementation
end
end
class LegacyPaymentGateway
def charge_card(card_details, amount_cents)
# Old API implementation
end
end
class PaymentGatewayAdapter
def initialize(gateway)
@gateway = gateway
end
def process_payment(amount, payment_info)
if @gateway.respond_to?(:process)
@gateway.process(amount, payment_info[:token])
else
amount_cents = (amount * 100).to_i
@gateway.charge_card(payment_info[:card_details], amount_cents)
end
end
end
Builder Pattern hides complex object construction behind a fluent interface. The builder manages construction steps and validation internally.
class QueryBuilder
def initialize
@table = nil
@columns = []
@conditions = []
@joins = []
@order_by = []
end
def from(table)
@table = table
self
end
def select(*columns)
@columns.concat(columns)
self
end
def where(condition)
@conditions << condition
self
end
def join(table, condition)
@joins << { table: table, condition: condition }
self
end
def order_by(column, direction = :asc)
@order_by << { column: column, direction: direction }
self
end
def build
validate_query
construct_sql
end
private
def validate_query
raise "Table name required" unless @table
end
def construct_sql
sql = "SELECT #{build_select_clause} FROM #{@table}"
sql += build_joins_clause if @joins.any?
sql += build_where_clause if @conditions.any?
sql += build_order_clause if @order_by.any?
sql
end
def build_select_clause
@columns.empty? ? '*' : @columns.join(', ')
end
def build_joins_clause
@joins.map { |j| " JOIN #{j[:table]} ON #{j[:condition]}" }.join
end
def build_where_clause
" WHERE " + @conditions.join(' AND ')
end
def build_order_clause
order_parts = @order_by.map { |o| "#{o[:column]} #{o[:direction].upcase}" }
" ORDER BY " + order_parts.join(', ')
end
end
Common Pitfalls
Information hiding fails when implementation details leak through interfaces or when access control mechanisms are misused.
Returning Mutable Internal State breaks encapsulation by allowing external code to modify private data structures.
# Problematic: Returns internal array reference
class TodoList
def initialize
@items = []
end
def items
@items # External code can call @items.clear
end
end
# Fixed: Returns defensive copy
class TodoList
def initialize
@items = []
end
def items
@items.dup
end
def each_item(&block)
@items.each(&block)
end
end
Accepting Mutable Parameters creates similar problems when storing references to objects that external code retains and modifies.
# Problematic: Stores reference to external array
class Configuration
def initialize(settings)
@settings = settings # External code keeps reference
end
end
# Fixed: Creates defensive copy
class Configuration
def initialize(settings)
@settings = settings.dup
end
end
Overusing Public Methods creates large interfaces that constrain future changes. Methods become public by default in Ruby, leading developers to leave methods public unnecessarily.
# Problematic: Everything public
class DataProcessor
def initialize
@cache = {}
end
def process(data)
cached = lookup_cache(data)
return cached if cached
result = perform_calculation(data)
store_cache(data, result)
result
end
def lookup_cache(data)
@cache[data]
end
def perform_calculation(data)
# Complex calculation
end
def store_cache(data, result)
@cache[data] = result
end
end
# Fixed: Minimal public interface
class DataProcessor
def initialize
@cache = {}
end
def process(data)
cached = lookup_cache(data)
return cached if cached
result = perform_calculation(data)
store_cache(data, result)
result
end
private
def lookup_cache(data)
@cache[data]
end
def perform_calculation(data)
# Complex calculation
end
def store_cache(data, result)
@cache[data] = result
end
end
Exposing Implementation Types couples clients to internal representations. Public methods that return or accept implementation-specific types create dependencies on those types.
# Problematic: Exposes hash structure
class UserDatabase
def get_user_record(id)
@users[id] # Returns hash with specific structure
end
end
# Fixed: Returns domain object
class UserDatabase
def find_user(id)
data = @users[id]
return nil unless data
User.new(data)
end
end
Misunderstanding Protected leads to confusion about visibility. Protected methods can be called on other instances of the same class, which sometimes exposes more than intended.
class BankAccount
def initialize(balance)
@balance = balance
end
def transfer_to(recipient, amount)
return false if @balance < amount
@balance -= amount
recipient.receive_deposit(amount) # Can call protected method
true
end
protected
def receive_deposit(amount)
@balance += amount
end
end
Testing Private Methods Directly creates dependencies on implementation details in tests. Tests should verify behavior through public interfaces, not test private methods directly.
# Problematic: Tests private implementation
class CalculatorTest < Minitest::Test
def test_internal_calculation
calc = Calculator.new
result = calc.send(:internal_calculation, 5) # Testing private method
assert_equal 25, result
end
end
# Fixed: Tests public behavior
class CalculatorTest < Minitest::Test
def test_calculation_result
calc = Calculator.new
result = calc.calculate(5)
assert_equal 25, result
end
end
Getter/Setter Proliferation defeats encapsulation when every instance variable gets automatic accessor methods. This creates no real boundary between external and internal representation.
# Problematic: No real encapsulation
class Rectangle
attr_accessor :width, :height, :x, :y, :color, :border_width
end
# Better: Controlled interface
class Rectangle
def initialize(x, y, width, height)
@x = x
@y = y
@width = width
@height = height
@color = :black
@border_width = 1
end
def move(dx, dy)
@x += dx
@y += dy
end
def resize(new_width, new_height)
@width = new_width if new_width > 0
@height = new_height if new_height > 0
end
def area
@width * @height
end
end
Reference
Ruby Method Visibility
| Visibility Level | Accessible From | Use Case |
|---|---|---|
| public | Any code with object reference | External interface, public API |
| private | Same instance only | Internal implementation, helper methods |
| protected | Same class and subclasses | Inter-instance communication, template methods |
Access Control Syntax
| Syntax Pattern | Effect | Example |
|---|---|---|
| private | All following methods private | private |
| private :method_name | Specific method private | private :calculate |
| private def method_name | Method definition private | private def calculate |
Information Hiding Checklist
| Aspect | Guideline |
|---|---|
| Instance Variables | Never expose directly, use accessor methods |
| Public Methods | Keep minimal, each method is long-term commitment |
| Private Methods | Mark explicitly, can change freely |
| Return Values | Return copies or immutable objects for internal state |
| Parameters | Copy mutable parameters before storing |
| Error Messages | Avoid exposing internal structure details |
| Method Names | Describe what, not how |
Common Interface Patterns
| Pattern | Purpose | Visibility |
|---|---|---|
| Query Methods | Return information without side effects | Public |
| Command Methods | Modify state, return success indication | Public |
| Factory Methods | Create and configure objects | Public |
| Template Methods | Define algorithm structure | Public/Protected |
| Helper Methods | Support public interface | Private |
| Validation Methods | Check preconditions | Private |
| Callback Methods | Hook points for subclasses | Protected |
Design Guidelines
| Principle | Implementation |
|---|---|
| Minimal Interface | Expose only essential operations |
| Stable Contracts | Public interface should rarely change |
| Implementation Hiding | Private methods can change freely |
| Type Abstraction | Return domain objects, not data structures |
| Defensive Copying | Copy mutable data crossing boundaries |
| Single Responsibility | Each public method has one clear purpose |
| Fail Fast | Validate parameters in public methods |
Encapsulation Strategies
| Strategy | Application | Benefit |
|---|---|---|
| Value Objects | Wrap primitive types | Type safety, validation |
| Builder Pattern | Complex construction | Hide construction complexity |
| Facade | Subsystem access | Simplify complex interfaces |
| Strategy | Algorithm variation | Hide algorithm implementation |
| Template Method | Algorithm structure | Control variation points |
| Adapter | Interface translation | Hide incompatible interfaces |