<?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: Cory Meikle</title>
    <description>The latest articles on DEV Community by Cory Meikle (@coryrin).</description>
    <link>https://dev.to/coryrin</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F845505%2Fad3e5d68-6b72-42f6-9525-aec70b0d8fce.jpeg</url>
      <title>DEV Community: Cory Meikle</title>
      <link>https://dev.to/coryrin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/coryrin"/>
    <language>en</language>
    <item>
      <title>Composition over Inheritance — it’s not always one or the other!</title>
      <dc:creator>Cory Meikle</dc:creator>
      <pubDate>Mon, 25 Aug 2025 14:31:58 +0000</pubDate>
      <link>https://dev.to/coryrin/composition-over-inheritance-its-not-always-one-or-the-other-5119</link>
      <guid>https://dev.to/coryrin/composition-over-inheritance-its-not-always-one-or-the-other-5119</guid>
      <description>&lt;p&gt;You may have heard "Prefer composition over inheritance" shouted about a lot in software design, it's something that's been accepted for a very long time. Inheritance has a bad name due to how easy it is to end up with a bad, fragile hierarchy.&lt;/p&gt;

&lt;p&gt;Instead of the typical discussion of this topic using Dog/Cat/Animal classes, let's use a real world example, using a payment gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inheritance for Consistency
&lt;/h2&gt;

&lt;p&gt;Before using inheritance, you must ask yourself if you ever see the core logic having to behave in a different way. Now, in our case from the payment gateway integration, we will always be calling our payment provider via HTTP. Meaning there will always be Headers present, and always an Endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3y85ul3dg74imzu53jp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3y85ul3dg74imzu53jp.png" alt=" " width="800" height="295"&gt;&lt;/a&gt;&lt;br&gt;
The above diagram shows &lt;code&gt;AbstractPaymentRequest&lt;/code&gt; at the top, enforcing consistency across all requests. The child classes only need to worry about implementing the endpoint they call, and the payload they need to send.&lt;/p&gt;

&lt;p&gt;In our example (Actually using &lt;a href="https://developer.elavon.com/products/en-uk/opayo/v1/api-reference" rel="noopener noreferrer"&gt;Opayo&lt;/a&gt;) the headers we need to supply on all of our requests are Authentication (Basic auth) and typically the Content-Type. So, this is a job for the Abstract method.&lt;/p&gt;

&lt;p&gt;Once more, the send() method is the concrete implementation of building the request object and sending the request to the Opayo server. This returns to us a &lt;code&gt;PaymentResponse&lt;/code&gt; class (Not implemented in the diagram, just think of it as a value class that parses the response we retrieve from the API).&lt;/p&gt;

&lt;p&gt;This is also referred to as the &lt;a href="https://refactoring.guru/design-patterns/template-method" rel="noopener noreferrer"&gt;Template Method Pattern&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What does this achieve?
&lt;/h2&gt;

&lt;p&gt;What we've achieved here is the knowledge that any class that extends our AbstractPaymentRequest will be sent via HTTP, and all of the authentication will be handled by its parent. In addition to this, it also means that to call a new endpoint on Opayo we simply need to create a new class that extends the AbstractPaymentRequest. This means we also adhere to the &lt;a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle" rel="noopener noreferrer"&gt;Open-Closed Principle&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where does composition come into this?
&lt;/h2&gt;

&lt;p&gt;If we look at the &lt;a href="https://developer.elavon.com/products/en-uk/opayo/v1/api-reference#tag/Transactions/operation/createTransaction" rel="noopener noreferrer"&gt;Opayo API documentation&lt;/a&gt;, primarily under Transactions, there's a transaction type of "Payment". You don't need to look at all of the expected payload, but specifically the "paymentMethod" object it expects.&lt;/p&gt;

&lt;p&gt;It can take in different types of payment methods. Card, paypal, applePay, googlePay, etc.&lt;/p&gt;

&lt;p&gt;It would be pretty bad if we had logic within our CreatePayment class to change the paymentMethod object based on our input. It's a bit of a code smell, and it also means we'd have to update the class every time we add a new payment method, meaning if we one day added google pay we'd need to do regression testing.&lt;/p&gt;

&lt;p&gt;This is where composition comes in to assist us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentRequest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractPaymentRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;PaymentMethodBuilder&lt;/span&gt; &lt;span class="nv"&gt;$paymentMethodBuilder&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getEndpoint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'/api/v1/transactions'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
            &lt;span class="s1"&gt;'paymentMethod'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;paymentMethodBuilder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="c1"&gt;// rest left off for ease of reading&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PaymentMethodBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CardPaymentBuilder&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentMethodBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// implementation of payload for card payment&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GooglePayBuilder&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PaymentMethodBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// implementation of payload for google pay payment&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see above, we have an interface &lt;code&gt;PaymentMethodBuilder&lt;/code&gt; and currently 2 classes, &lt;code&gt;CardPaymentBuilder&lt;/code&gt; and &lt;code&gt;GooglePayBuilder&lt;/code&gt;, both of these classes are simply responsible for building the payload that Opayo expects of their respective methods.&lt;/p&gt;

&lt;p&gt;Within the &lt;code&gt;CreatePayment&lt;/code&gt; class we can see it takes in the &lt;code&gt;PaymentMethodBuilder&lt;/code&gt; on construction, which means we can easily swap the payment method we use based on what the user has selected on the frontend, all without bloating our request class with logic of swapping, and sticking to the Open-Closed principle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The point here isn't that inheritance is bad or that composition is always better. It's that they serve different purposes, and it's not always one over the other. I hope I've made that clear here.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>designpatterns</category>
      <category>php</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
