<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: INIYAN K</title>
    <description>The latest articles on DEV Community by INIYAN K (@iniyan_17).</description>
    <link>https://dev.to/iniyan_17</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4066820%2F5d4f23a9-f2e5-4161-a67b-23ff40c695ff.png</url>
      <title>DEV Community: INIYAN K</title>
      <link>https://dev.to/iniyan_17</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iniyan_17"/>
    <language>en</language>
    <item>
      <title>Prototype &amp; Factory Method Design Patterns in Java - ClauseGuard</title>
      <dc:creator>INIYAN K</dc:creator>
      <pubDate>Fri, 07 Aug 2026 06:00:35 +0000</pubDate>
      <link>https://dev.to/iniyan_17/prototype-factory-method-design-patterns-in-java-clauseguard-2alp</link>
      <guid>https://dev.to/iniyan_17/prototype-factory-method-design-patterns-in-java-clauseguard-2alp</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;ClauseGuard lets legal teams upload a contract — PDF or DOCX — and get back something far more useful than a raw document: individually segmented clauses, each classified by type, scored for risk, and surfaced on a dashboard the moment analysis finishes.&lt;/p&gt;

&lt;p&gt;Under the hood, three classic design patterns quietly do most of the structural work: the &lt;strong&gt;Adapter&lt;/strong&gt; pattern lets two completely different document-parsing libraries (PDFBox and Apache POI) speak one common language, the &lt;strong&gt;Factory&lt;/strong&gt; pattern decides which of those adapters to build in the first place, and the &lt;strong&gt;Observer&lt;/strong&gt; pattern broadcasts the finished analysis to the dashboard, the audit log, and the alerting system all at once. This post walks through each one, why we needed it, and how they fit together into a single pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Factory decides which extractor to build based on file type, Adapter makes PDFBox and Apache POI speak the same interface, and Observer broadcasts the finished analysis to everyone who needs to react to it. Together they let us add a new file format or a new analysis listener without touching the core pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  📑 Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Adapter Pattern — Unifying Document Text Extraction&lt;/li&gt;
&lt;li&gt;The Factory Pattern — Choosing the Right Extractor&lt;/li&gt;
&lt;li&gt;The Observer Pattern — Broadcasting Analysis Results&lt;/li&gt;
&lt;li&gt;How the Three Patterns Work Together&lt;/li&gt;
&lt;li&gt;Performance Metrics&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. The Adapter Pattern — Unifying Document Text Extraction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;A contract can arrive as a PDF or a DOCX, and the two libraries ClauseGuard uses to read them — Apache PDFBox and Apache POI — don't agree on much. PDFBox wants a &lt;code&gt;PDDocument&lt;/code&gt; loaded from a file stream and hands text back through a &lt;code&gt;PDFTextStripper&lt;/code&gt;. POI wants an &lt;code&gt;XWPFDocument&lt;/code&gt; and hands text back paragraph by paragraph. If the Clause Segmentation Engine had to know the difference, every file-type quirk would leak straight into the core pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;The Adapter pattern wraps each library behind one common interface, &lt;code&gt;TextExtractor&lt;/code&gt;, so the rest of the system only ever calls &lt;code&gt;.extract(file)&lt;/code&gt; — regardless of what's happening underneath.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextExtractor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PDFExtractorAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextExtractor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdf_box_lib&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf_box_lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf_box_lib&lt;/span&gt;   &lt;span class="c1"&gt;# the mismatched third-party API
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf_box_lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf_box_lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DOCXExtractorAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextExtractor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poi_lib&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poi_lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;poi_lib&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poi_lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open_xwpf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_paragraphs&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ContractIngestionService&lt;/code&gt; (the client) only ever depends on &lt;code&gt;TextExtractor&lt;/code&gt;. It has no idea &lt;code&gt;PDDocument&lt;/code&gt; or &lt;code&gt;XWPFDocument&lt;/code&gt; even exist — that detail is hidden entirely inside the adapter.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Diagram: Target/Adapter on top (&lt;code&gt;TextExtractor&lt;/code&gt;), Client (&lt;code&gt;ContractIngestionService&lt;/code&gt;) and Adaptees below — &lt;code&gt;PDFExtractorAdapter&lt;/code&gt; and &lt;code&gt;DOCXExtractorAdapter&lt;/code&gt; translate PDFBox and POI into the app's own interface.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🔌 Swap or upgrade a parsing library without changing &lt;code&gt;ContractIngestionService&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;🧩 Library-specific quirks (PDFBox's stripper config, POI's paragraph model) stay contained in one adapter class instead of leaking everywhere&lt;/li&gt;
&lt;li&gt;🧪 Makes the ingestion layer easy to unit-test with mock extractors, without needing a real PDF or DOCX file on disk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 &lt;strong&gt;Key takeaway:&lt;/strong&gt; The adapter is a translator, not a decision-maker. It doesn't decide &lt;em&gt;whether&lt;/em&gt; to extract text — it just makes sure the extraction happens the same way no matter which library is doing the work underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Factory Pattern — Choosing the Right Extractor
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Even with a clean &lt;code&gt;TextExtractor&lt;/code&gt; interface, something still has to decide &lt;em&gt;which&lt;/em&gt; concrete adapter to instantiate — &lt;code&gt;PDFExtractorAdapter&lt;/code&gt; or &lt;code&gt;DOCXExtractorAdapter&lt;/code&gt; — based on the uploaded file's extension. Scattering that decision (&lt;code&gt;if filename.endswith(".pdf"): ... elif filename.endswith(".docx"): ...&lt;/code&gt;) across upload handlers, retry logic, and re-processing jobs is exactly the kind of duplication design patterns exist to prevent.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;TextExtractorFactory&lt;/code&gt; centralizes that decision in one place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextExtractorFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_extractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PDFExtractorAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PDFBoxLib&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.docx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;DOCXExtractorAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApachePOILib&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unsupported file type: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ContractIngestionService&lt;/code&gt; never touches &lt;code&gt;PDFExtractorAdapter&lt;/code&gt; or &lt;code&gt;DOCXExtractorAdapter&lt;/code&gt; by name — it just asks the factory for an extractor and uses it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Client code — no idea which class it's actually talking to
&lt;/span&gt;&lt;span class="n"&gt;extractor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextExtractorFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_extractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;raw_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Diagram: &lt;code&gt;TextExtractorFactory&lt;/code&gt; decides which concrete &lt;code&gt;TextExtractor&lt;/code&gt; to build — &lt;code&gt;PDFExtractorAdapter&lt;/code&gt; or &lt;code&gt;DOCXExtractorAdapter&lt;/code&gt; — based on the uploaded file's extension.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🎯 One place to update when a new file type is added (e.g., plain &lt;code&gt;.txt&lt;/code&gt; support) — not a dozen scattered conditionals&lt;/li&gt;
&lt;li&gt;🧱 &lt;code&gt;ContractIngestionService&lt;/code&gt; depends only on the &lt;code&gt;TextExtractor&lt;/code&gt; interface, never on concrete classes&lt;/li&gt;
&lt;li&gt;🔀 Makes it trivial to reject unsupported formats early, with a single, consistent error path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 &lt;strong&gt;Key takeaway:&lt;/strong&gt; The factory is the only piece of code in the entire app that's allowed to say &lt;code&gt;new PDFExtractorAdapter()&lt;/code&gt;. Everyone else just asks for "an extractor for this file" and trusts the factory to hand back something that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Observer Pattern — Broadcasting Analysis Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;When a contract finishes analysis — classified, risk-scored, and categorized — several things need to happen at once: the audit log needs a permanent record, the risk dashboard needs to update its flagged-clause list, and if a clause came back High Risk, an alert needs to go out to configured administrators. Hard-coding all of that inside the analysis engine would tightly couple scoring logic to logging logic, dashboard logic, and notification logic — a maintenance nightmare the moment we add a new kind of listener.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;The Observer pattern decouples the &lt;code&gt;AnalysisEngine&lt;/code&gt; (the thing that changes) from everything that needs to react to that change. &lt;code&gt;AnalysisEngine&lt;/code&gt; doesn't know or care what &lt;code&gt;AuditLogger&lt;/code&gt; or &lt;code&gt;AlertNotifier&lt;/code&gt; actually does — it just calls &lt;code&gt;update()&lt;/code&gt; on whoever is registered.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AuditLog: recorded analysis for&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contract_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RiskDashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dashboard: refreshing flagged clauses for&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contract_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AlertNotifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_high_risk_clause&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alert: High-risk clause detected in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contract_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnalysisEngine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_observer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;analysis_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Diagram: Subject/Observer interfaces at top, with &lt;code&gt;AnalysisEngine&lt;/code&gt; as the ConcreteSubject and &lt;code&gt;AuditLogger&lt;/code&gt;/&lt;code&gt;RiskDashboard&lt;/code&gt;/&lt;code&gt;AlertNotifier&lt;/code&gt; as ConcreteObservers.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works in ClauseGuard
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;AnalysisEngine&lt;/code&gt; → &lt;em&gt;"Analysis complete → Contract #482, 3 clauses flagged High Risk"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Observers receive that notification and each reacts in its own way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📝 &lt;strong&gt;AuditLogger&lt;/strong&gt; writes an immutable log entry before anything else happens&lt;/li&gt;
&lt;li&gt;🖥️ &lt;strong&gt;RiskDashboard&lt;/strong&gt; refreshes the live flagged-clause view&lt;/li&gt;
&lt;li&gt;🚨 &lt;strong&gt;AlertNotifier&lt;/strong&gt; fires an email/webhook to configured admins if a High Risk clause was found&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;ReportService&lt;/strong&gt; queues the result for the next scheduled Contract Summary export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding a new kind of listener — say, a Slack notifier or a compliance-export trigger — never requires touching &lt;code&gt;AnalysisEngine&lt;/code&gt;. We just implement &lt;code&gt;Observer&lt;/code&gt; and call &lt;code&gt;add_observer()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🔗 Loose coupling between the analysis engine and everything watching it&lt;/li&gt;
&lt;li&gt;➕ New observers can be added without modifying existing code (open/closed principle)&lt;/li&gt;
&lt;li&gt;🔄 Every observer gets the result at the same time, so the audit log is never written &lt;em&gt;after&lt;/em&gt; a risky clause has already been shown to a user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 &lt;strong&gt;Key takeaway:&lt;/strong&gt; &lt;code&gt;AnalysisEngine&lt;/code&gt; never says the word "email," "dashboard," or "log." It just says "analysis finished, here's the result" — and lets each observer decide what that means for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Three Patterns Work Together
&lt;/h2&gt;

&lt;p&gt;These aren't three isolated exercises — they form a pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Factory&lt;/strong&gt; decides which extractor to build — &lt;code&gt;TextExtractorFactory.create_extractor("contract.pdf")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adapter&lt;/strong&gt; makes that extractor speak the app's common language — &lt;code&gt;PDFExtractorAdapter&lt;/code&gt; implements &lt;code&gt;TextExtractor&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observer&lt;/strong&gt; decides who reacts once analysis is done — &lt;code&gt;AnalysisEngine.notify()&lt;/code&gt; fires to every registered listener&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The chain, step by step: a contract is uploaded → &lt;code&gt;TextExtractorFactory&lt;/code&gt; inspects the filename and hands back the right adapter → the adapter extracts raw text through a single &lt;code&gt;.extract()&lt;/code&gt; call regardless of the underlying library → segmentation, classification, and risk scoring run on that text → &lt;code&gt;AnalysisEngine.notify()&lt;/code&gt; fires once the result is ready → &lt;code&gt;AuditLogger&lt;/code&gt;, &lt;code&gt;RiskDashboard&lt;/code&gt;, and &lt;code&gt;AlertNotifier&lt;/code&gt; all react independently, in any order, with zero knowledge of each other.&lt;/p&gt;

&lt;p&gt;A contract lands in the system → the Factory picks the right tool for the file type → the Adapter makes that tool speak a language the rest of the pipeline understands → the Observer pattern broadcasts the finished result to everyone who needs to know. Three patterns, one clean chain of responsibility — no piece knows more than it needs to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Metrics of the Three Patterns
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Adapter&lt;/th&gt;
&lt;th&gt;Factory&lt;/th&gt;
&lt;th&gt;Observer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintainability&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code Reusability&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility&lt;/td&gt;
&lt;td&gt;Supports any current or future document-parsing library&lt;/td&gt;
&lt;td&gt;Supports adding new file formats with one new branch/class&lt;/td&gt;
&lt;td&gt;Supports multiple listener modules (audit, dashboard, alerts, reporting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coupling&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extensibility&lt;/td&gt;
&lt;td&gt;Easy to add a new format-specific adapter&lt;/td&gt;
&lt;td&gt;Easy to add new extractor types&lt;/td&gt;
&lt;td&gt;Easy to add new observers without touching &lt;code&gt;AnalysisEngine&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;None of these patterns exist for their own sake — each one solves a concrete problem ClauseGuard actually has: dealing with two incompatible document-parsing libraries (Adapter), centralizing which extractor gets built for a given file type (Factory), and keeping the audit log, dashboard, and alerting system in sync the instant an analysis finishes (Observer). Used together, they let us support a new file format, a new alerting channel, or a new kind of downstream listener without rewriting the core ingestion-to-risk-scoring pipeline — which is really the whole point of a design pattern: not cleverness for its own sake, but code that's easy to extend and hard to break.&lt;/p&gt;

</description>
      <category>designpatterns</category>
      <category>java</category>
      <category>cleancode</category>
      <category>clauseguard</category>
    </item>
  </channel>
</rss>
