W8_L5: Design Patterns β€” Structural#

Video W8_L5: Design Patterns - Structural
Channel IIT Madras β€” B.S. Degree Programme (Software Engineering)
Duration 16:12
Covers Facade pattern, Adapter pattern

0. Where structural patterns sit β€” 00:27#

The previous lecture covered creational patterns, which "provide object creation mechanisms β€” they help us create objects in an efficient manner."

This lecture covers structural patterns, which:

help us assemble objects and classes into larger structures, and help us keep these structures flexible and efficient.

Types of design patterns
Types of design patterns

Type Concern
Creational Used during the process of object creation
Structural ← this lecture Composition of classes or objects
Behavioural How classes/objects interact and distribute responsibility

Two structural patterns are taught here:

  1. Facade β€” 01:12
  2. Adapter β€” 09:03

1. Facade Design Pattern#

1.1 The problem β€” 01:22#

Facade problem slide
Facade problem slide

You have a task that requires you to:

  • Create several objects + perform several steps β€” initialize many objects, execute methods in the correct order, pass the right parameters
  • Use existing complicated library functions
  • …and as a result, others reusing your code must know details about which objects to create and which library functions to call

That last point is the real cost: the complexity leaks out to the client, and using your code becomes difficult.

1.2 Concrete example β€” the seller portal β€” 02:26#

After a buyer places an order, the system must do four things:

Seller portal example
Seller portal example

  1. Create order
  2. Notify seller
  3. Prepare packaging
  4. Send out for delivery

1.3 The naive implementation ("before") β€” 03:03#

Four independent classes, each doing one job:

Four classes before facade
Four classes before facade

class Order {
    public void placeOrder(String product){
        System.out.println("Order placed for Product " + product);
    }
}

class Seller {
    public void sellerNotification(String sellerName){
        System.out.println("Notified Seller " + sellerName);
    }
}

class Package {
    public void packageOrder(String packagingType){
        System.out.println("Order packed with " + packagingType);
    }
}

class Delivery {
    public void deliver(String buyerName, String address) {
        System.out.println("Sent package to delivery to " + buyerName
                           + " with address " + address);
    }
}

And the client's main has to do all of this by hand, in the right order:

Client main before facade
Client main before facade

public class FacadeBefore {
    public static void main(String[] args) {
        Order o = new Order();
        o.placeOrder("Lenovo Laptop");

        Seller s = new Seller();
        s.sellerNotification("Seller 1");

        Package p = new Package();
        p.packageOrder("bubble wrap");

        Delivery d = new Delivery();
        d.deliver("abc", "xyz");
    }
}

Reflection question posed at 04:15: how can I make this better for the client? The client "should necessarily not need to know the exact steps which are required."

1.4 The pattern β€” 04:47#

Facade design pattern: provides a simple interface to a library, or a complex set of classes.

Facade UML
Facade UML

You create one class with one method that internally calls all the required functions across all the required classes. Clients call only that method.

doSomething() {
    Class1 c1 = new Class1();
    Class2 c2 = new Class2();
    Class3 c3 = new Class3();
    c1.doStuff(c2)
    c3.setX(c1.getX());
    return c3.getY();
}

Client1 and Client2 both call FaΓ§ade.doSomething(). Neither knows that package1.Class1, package2.Class2, package3.Class3 exist.

1.5 The refactored code ("after") β€” 06:00#

Before vs after side by side
Before vs after side by side

The individual classes do not change. Order, Seller, Package, Delivery stay exactly as they were. You only add a facade class:

class DeliveryFacade {
    public void deliver(String product, String seller, String packagingType,
                        String buyerName, String address) {
        Order o = new Order();
        o.placeOrder(product);

        Seller s = new Seller();
        s.sellerNotification(seller);

        Package p = new Package();
        p.packageOrder(packagingType);

        Delivery d = new Delivery();
        d.deliver(buyerName, address);
    }
}

public class FacadeAfter {
    public static void main(String[] args) {
        DeliveryFacade d = new DeliveryFacade();
        d.deliver("Lenovo Laptop", "Seller 1", /* packaging, buyer, address */ ...);
    }
}

The client now creates one object and calls one function. It doesn't remember which objects to create, nor the exact order of calls.

Before vs After#

Before After
Objects client creates 4 (Order, Seller, Package, Delivery) 1 (DeliveryFacade)
Method calls client makes 4, in a specific order 1 (deliver(...))
Client must know call order? Yes No
Worker classes changed? β€” No β€” facade is purely additive

1.6 Pros and cons β€” 07:46#

Facade pros and cons
Facade pros and cons

Pros Cons
Isolates code from other libraries'/classes' complexity β€” clients are exposed to just a single method Tightly coupled to other objects, so maintenance becomes more difficult

The coupling cost, shown concretely β€” DeliveryFacade references Order, Seller, Package and Delivery. If any one of those classes or its methods changes, DeliveryFacade must change too:

Facade coupling
Facade coupling


2. Adapter Design Pattern#

2.1 The problem: incompatible interfaces β€” 09:13#

Adapter problem
Adapter problem

On the seller portal, some products have cost in dollars or euros, so conversion to rupees is needed.

  • There is an existing ConversionCalculator class/library β€” you do not have control over it.
  • The Product class cannot directly access it: its interface is incompatible.
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Product            β”‚              β”‚      ConversionCalculator        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€              β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ - productId: String         β”‚              β”‚                                  β”‚
β”‚ - productName: String       β”‚   ─── βœ— ───  β”‚ + dollarsToRupee(money): double  β”‚
β”‚ - productType: String       β”‚              β”‚ + eurosToRupee(money): double    β”‚
β”‚ - productCost: double       β”‚              β”‚                                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ + addProduct(id,name,type,  β”‚
β”‚              cost)          β”‚
β”‚ + getProductCost(): double  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2.2 The pattern β€” 10:24#

Adapter UML
Adapter UML

The client wants to call methodB() but can't reach it. So insert an Adapter that acts as a wrapper / link between client and adaptee:

Role What it does
Client Calls adaptor.methodA() β€” the only thing it knows
Adaptor Holds a reference adaptee: Adaptee; its methodA() calls adaptee.methodB()
Adaptee The existing, uncontrollable class exposing methodB()

The two methods might be incompatible with each other, but that issue of incompatibility is taken care of by the adapter. The adapter wraps one of the objects to hide the complexity of what is happening behind the scenes.

2.3 Implementation β€” 12:00#

The adaptee β€” an existing library class with the actual conversion rates:

ConversionCalculator class
ConversionCalculator class

class ConversionCalculator {
    public double dollarsToRupee(double money) {
        return (money * 75);
    }
    public double eurosToRupees(double money) {
        return (money * 80);
    }
}

The adapter β€” bridges Product and ConversionCalculator. It takes a Product p, creates a ConversionCalculator, and passes the product's cost into the right function:

Adapter class and addProduct
Adapter class and addProduct

class CostCalculatorAdapter {
    public double getProductCost(Product p) {
        ConversionCalculator calc = new ConversionCalculator();
        return calc.dollarsToRupee(p.getProductCost());
    }
}

The client β€” Product.addProduct() creates an adapter object and calls the adapter's function. Product needs to know nothing about ConversionCalculator:

public void addProduct(String productId, String name, String type, double cost){
    this.productId   = productId;
    this.productName = name;
    this.productType = type;
    this.productCost = cost;

    CostCalculatorAdapter adapter = new CostCalculatorAdapter();
    this.productCost = adapter.getProductCost(this);
}

2.4 Running it β€” 14:09#

Adapter main and output
Adapter main and output

public class Adapter {
    public static void main(String[] args) {
        //Product from US
        Product p = new Product();
        p.addProduct("1", "Product 1", "Book", 300.00);

        System.out.println("Product cost in Rupees== " + p.getProductCost());
    }
}
Product cost in Rupees== 22500.0

The cost was 300 (dollars); addProduct created the adapter, the adapter called dollarsToRupee, and 300 Γ— 75 = 22500 rupees came back.

2.5 Pros and cons β€” 14:23#

Adapter pros and cons
Adapter pros and cons

Pros Cons
Single Responsibility Principle β€” data-conversion code is separated from the primary business logic (Product's job is add/edit/delete products, not currency maths) Overall code complexity increases β€” a new class plus new methods for every adapter
Open/Closed Principle β€” new currencies just mean new adapter types; the class is open for extension If you could modify Product, or did have access to ConversionCalculator, you wouldn't need a separate class at all

3. Facade vs Adapter β€” the easy confusion#

Both wrap other classes and both hide complexity from a client. The difference is why:

Facade Adapter
Problem it solves Too many steps / objects for the client to manage Interfaces are incompatible β€” client literally cannot call the target
What it hides The number and order of calls The mismatch in interfaces
Wraps A whole subsystem (several classes) Typically one object (the adaptee)
Interface it exposes A brand-new, simplified one One that the existing client already expects
Would work without it? Yes β€” just verbosely No β€” the call is impossible
Example here DeliveryFacade.deliver(...) replacing 4 objects + 4 calls CostCalculatorAdapter letting Product reach ConversionCalculator
Main cost Tight coupling to the subsystem Extra class β†’ more complexity

4. Cheat sheet#

Structural patterns = composition of classes/objects into larger structures, kept flexible and efficient.

FACADE                                  ADAPTER

Client ──► Facade.doSomething()         Client ──► Adaptor.methodA()
              β”‚                                        β”‚
              β”œβ”€β–Ί Class1.m()                           └─► Adaptee.methodB()
              β”œβ”€β–Ί Class2.m()
              └─► Class3.m()            (incompatible interface bridged)
(many steps collapsed into one)
Pattern One-line definition Trigger to use it
Facade Provides a simple interface to a library or a complex set of classes Client must create many objects and call many methods in a fixed order
Adapter Provides a wrapper/link between a client and an incompatible adaptee You need a class you can't modify, and its interface doesn't match
Term Meaning
Adaptee The existing class you can't change (ConversionCalculator)
Adaptor The bridge class (CostCalculatorAdapter)
Facade The single-entry-point class (DeliveryFacade)

Principles referenced:

  • Single Responsibility Principle β€” a class should do one job (adapter keeps conversion out of Product)
  • Open/Closed Principle β€” open for extension; add new adapters without touching existing classes

5. File manifest#

w8-l5-design-patterns-structural/
β”œβ”€β”€ NOTES.md            # this file
β”œβ”€β”€ shots/              # 15 verified screenshots
β”œβ”€β”€ transcript_ts.txt   # timestamped transcript
β”œβ”€β”€ video.en.vtt        # raw subtitles
└── video.mkv           # source video (720p)

Commands used:

yt-dlp --skip-download --print "%(title)s | %(duration_string)s | %(uploader)s" URL

yt-dlp --skip-download --write-auto-subs --write-subs \
       --sub-lang en --sub-format vtt -o "video.%(ext)s" URL

python3 ~/.claude/skills/youtube-notes/scripts/vtt2txt.py video.en.vtt > transcript_ts.txt

yt-dlp -f "bestvideo[height<=720]+bestaudio/best[height<=720]" -o "video.%(ext)s" URL

~/.claude/skills/youtube-notes/scripts/grab_frames.sh video.mkv shots <<'EOF'
00:00:54|01_types_of_patterns
...
EOF