Reason #137 • May 17th, 2026

Classes are factories

In Ruby, classes are objects responding to the .new method for instantiation, so we can freely pass them to collaborators and use them as "factories", without having to bother with the rigmarole of the factory design pattern as commonly seen in statically typed object oriented languages like Java:

Ruby
Invoice = Struct.new(:number, :customer, :total)

class PdfRenderer
  def initialize(invoice)
    @invoice = invoice
  end

  def render
    "PDF invoice #{@invoice.number} for #{@invoice.customer}"
  end
end

class CsvRenderer
  def initialize(invoice)
    @invoice = invoice
  end

  def render
    [
      @invoice.number,
      @invoice.customer,
      @invoice.total,
    ].join(",")
  end
end

class InvoiceExporter
  def initialize(renderer_classes)
    @renderer_classes = renderer_classes
  end

  def export(invoice)
    @renderer_classes.map do |renderer_class|
      renderer_class.new(invoice).render
    end
  end
end

invoice = Invoice.new(
  number: "INV-123",
  customer: "Acme AB",
  total: 1200,
)

exporter = InvoiceExporter.new([PdfRenderer, CsvRenderer])
puts exporter.export(invoice)

# Output:
# PDF invoice INV-123 for Acme AB
# INV-123,Acme AB,1200
      
Java
import java.util.ArrayList;
import java.util.List;

class Invoice {
  final String number;
  final String customer;
  final int total;

  Invoice(String number, String customer, int total) {
    this.number = number;
    this.customer = customer;
    this.total = total;
  }
}

interface InvoiceRenderer {
  String render();
}

interface InvoiceRendererFactory {
  InvoiceRenderer create(Invoice invoice);
}

class PdfRenderer implements InvoiceRenderer {
  private final Invoice invoice;

  PdfRenderer(Invoice invoice) {
    this.invoice = invoice;
  }

  public String render() {
    return "PDF invoice " + invoice.number + " for " + invoice.customer;
  }
}

class CsvRenderer implements InvoiceRenderer {
  private final Invoice invoice;

  CsvRenderer(Invoice invoice) {
    this.invoice = invoice;
  }

  public String render() {
    return invoice.number + "," + invoice.customer + "," + invoice.total;
  }
}

class PdfRendererFactory implements InvoiceRendererFactory {
  public InvoiceRenderer create(Invoice invoice) {
    return new PdfRenderer(invoice);
  }
}

class CsvRendererFactory implements InvoiceRendererFactory {
  public InvoiceRenderer create(Invoice invoice) {
    return new CsvRenderer(invoice);
  }
}

class InvoiceExporter {
  private final List<InvoiceRendererFactory> factories;

  InvoiceExporter(List<InvoiceRendererFactory> factories) {
    this.factories = factories;
  }

  List<String> export(Invoice invoice) {
    List<String> files = new ArrayList<>();

    for (InvoiceRendererFactory factory : factories) {
      files.add(factory.create(invoice).render());
    }

    return files;
  }
}

public class Main {
  public static void main(String[] args) {
    Invoice invoice = new Invoice("INV-123", "Acme AB", 1200);

    InvoiceExporter exporter = new InvoiceExporter(List.of(
      new PdfRendererFactory(),
      new CsvRendererFactory()
    ));

    for (String file : exporter.export(invoice)) {
      System.out.println(file);
    }

    // Output:
    // PDF invoice INV-123 for Acme AB
    // INV-123,Acme AB,1200
  }
}
      

Note how in the Java example, we have to define separate factory classes for each renderer, and then instantiate those factories to pass them to the InvoiceExporter. In contrast, in Ruby we can just pass the renderer classes directly. Also note how this allows us to express the same logic in less than half the lines of code.

To be fair to modern Java, simple cases like this can skip the factory pattern by means of method references, e.g. List.of(PdfRenderer::new, CsvRenderer::new) typed as List<Function<Invoice, InvoiceRenderer>>.

The more important lesson is to to KISS, whatever language we're using.

History

The ability to treat classes as first-class objects has been a core feature of Ruby since its inception