Service Design Pattern

I’m working with services and I found out there are at least 3 ways to use them inside controllers…

Statically: Like helper, Text::uppercase('foo')

Instancing it: $text = new Text(); $text->uppercase('foo');

Injecting with reflection through parameters:

public function(Text $text) {
    $text->uppercase('foo');
}

Injecting with reflection, but on the constructor:

private $text;
__construct(Text $text){
    $this->text = $text;
}

public function anyMethod() {
    $this->text->uppercase('foo');
}

A friend of mine, who works for a lot of time with this, told me that is an error to use it in different ways, and that he always uses the constructor method, even if there is such a simple thing as a string helper.

I’m a bit confused about this. From the pure and strong theory, what is the best way to work on this pattern?

The reference I’m talking about:

https://blog.quickadminpanel.com/laravel-when-to-use-dependency-injection-services-and-static-methods/

Sorry for the C# syntax, I’m not a PHP dev. But the core of the answer is language-agnostic, and I hope the syntax is readable enough for you to understand the focus of the examples.


You’ve actually done a great job of listing ways to have one class use another, ordered ascendingly by looseness of the coupling.

  • Hardcoding a reference (tightest coupling)
  • Passing a method parameter with the object reference
  • Passing a contructor parameter with the object reference (loosest coupling)

A friend of mine, who works for a lot of time with this, told me that is an error to use it in different ways, and that he always uses the constructor method, even if there is such a simple thing as a string helper.

Your friend is mostly, but not completely on the right track.

For good practice, you always want to promote loose coupling. Loose coupling means that it’s easier to swap out individual components when you need to. You might need to do so because you’re making a change to your codebase, and having things loosely coupled mean that you can more easily target a specific component and swap it out without affecting the other components.

Another great example of loose coupling is the ability to unit test your code. In this answer, I’m going to use unit testing as the main use case here to highlight how coupling affects the ease of development. Just remember that the benefits of loose coupling are much bigger than just testability.


Let’s say we’ve developed the following class, and it currently hardcodes a dependency on EmailSender:

public class MyService
{
    public int Add(int a, int b)
    {
        var result = a + b;

        var emailSender = new EmailSender();
        emailSender.SendEmail(
            "[email protected]", 
           $"Hey boss, I just calculated {a}+{b}, did you know that that is equal to {result}?"
        );

        return result;
    }
}

It’s a silly example, but it serves the purpose I need it to. We want to test this service. However, we don’t want to bombard our actual boss with emails about all our test runs.

While this is not relevant yet, I want you to look back at the code and think about what the consumer of this service needs to know to work with this service. All it needs to know is which numbers it needs to add, i.e. a and b. The consumer doesn’t know about the mailsender specifically or how to operate it.


By injecting the dependency, we make it possible to swap this mailsender out for a different one. When we run our test, we inject a fake mailsender which does not send an actual email, but does correctly confirm to us that someone tried to send an email.

Let’s inject it as a method parameter, like you suggested.

public class MyService
{
    public int Add(int a, int b, IEmailSender emailSender)
    {
        var result = a + b;

        emailSender.SendEmail(
            "[email protected]", 
           $"Hey boss, I just calculated {a}+{b}, did you know that that is equal to {result}?"
        );

        return result;
    }
}

public interface IEmailSender
{
    void SendEmail(string to, string message);
}

You can imagine the existence of a EmailSender class which actually sends out real emails.

Let’s look at what a fake mailsender would look like:

public class FakeMailSender : IEmailSender 
{
    public void SendEmail(string to, string message)
    {
        // Snitch about what someone tried to do
        Console.WriteLine($"Someone just tried to send an email to {to}");

        // DON'T SEND AN ACTUAL EMAIL
    }
}

If I now want to test if MyService is working correctly, I can run a test where I inject a FakeMailSender instead of a real EmailSender. This doesn’t affect the internal logic of MyService itself, which is what I’m testing. If MyService behaves correctly for a FakeMailSender, then I can rest assured that it will also behave correctly when I give it a real EmailSender (when deploying the code for real).


However… Remember when I pointed out that the consumer of MyService only needed to know the numbers they needed to add?

That is sadly no longer the case. Now, they need to have access to some IEmailSender object and pass that along before they can add two numbers together? That means putting more work into their hands. The consumer is not going to like that, and they might possibly not even be allowed to know that we are secretly sending emails.

This is why we inject dependencies via the constructor. The constructor is separate from the public methods, and therefore we are able to separate the construction of the object from actually using the constructed object, which is really useful when the construction requires specific information (e.g. what kind of mailsender to use) even though you want to keep the usage short and simple (e.g. only knowing which numbers you want to add up).

public class MyService
{
    private IEmailSender _emailSender;

    public MyService(IEmailSender emailSender)
    {
        _emailSender = emailSender;
    }

    public int Add(int a, int b)
    {
        var result = a + b;

        _emailSender.SendEmail(
            "[email protected]", 
           $"Hey boss, I just calculated {a}+{b}, did you know that that is equal to {result}?"
        );

        return result;
    }
}

Now, you might be thinking to yourself:

Wait a minute. Since the consumer would need to construct the MyService object anyway, it still needs to know what IEmailSender should be used, regardless of whether it passes it via the MyService constructor or the Add method itself.

Dependency injection is a recursive pattern. Just like how IEmailSender is injected into MyService instead of letting MyService create its own sender, your consumer will also get its MyService dependency injected, and it generally wouldn’t be creating its own MyService instance.

Just as a quick example, you’d expect the same dependency injection pattern for your consumer:

public class MyConsumer
{
    private MyService _myService;

    public MyConsumer(MyService myService)
    {
        _myService = myService;
    }

    public int CountFingers()
    {
        var totalFingers = myService.Add(leftHand.Fingers, rightHand.Fingers);
    }

Notice how the CountFingers method cares about how to add two numbers, but it doesn’t care one bit about IEmailSender objects. CountFingers is only interested in using a (created) service, it’s not interested in creating one.

If we had required CountFingers to have to pass the mail sender object as well, that would’ve significantly increased the complexity of the code, and the mail-sender-creating part of that code would’ve been a complete distraction from the true purpose of the method, i.e. counting the consumer’s fingers.


The reason I mentioned that your friend is mostly but not completely on track is that there are exceptions to this. His advice is generally correct but it’s not universally true.

1

One example is pure functions. These are static functions who track no state, and have no side effect except the value they return.

even if there is such a simple thing as a string helper

Helper methods are often this exact kind of pure function. One C# example here is String.Join:

var numbers = new int[] { 1, 2, 3, 4, 5 };

var result = String.Join(", ", numbers);

Console.WriteLine(result); // "1, 2, 3, 4, 5"

Pure functions, by virtue of being pure, generally escape the requirement of being context-specific and therefore being considered as a dependency that must be injected.

Just to be clear, pure functions exist in a lot of code bases, but they are few and far between, usually nothing more than trivial helper methods that people tend to gloss over. The reason your friend’s advice did not include this exception is possibly because the codebase he works in doesn’t have pure functions

2

Another example of an exception to using constructor-based dependency injection is when you want the dependency to be passed as a method parameter, because the consumer very specifically cares about choosing this dependency.

For example, if you want to change the behavior of a method. Let’s look at OrderBy, which sorts the elements of a collection based on a particular equality comparison.

var numbers = new int[] { 1, 4, 3, 2, 5 };

var orderedNumbers = numbers.OrderBy(x => x);

By default, it uses the default comparison logic, which in the case of integers is a simple a < b check. However, you are able to write your own comparer class, which uses a custom comparison logic. For example, you might want to sort numbers not by their size, but by how many factors you get when you factorize them.

var numbersOrderedByFactorization = numbers.OrderBy(x => x, new FactorizationComparer());

By injecting this comparer via the method, the consumer is able to very directly control how the method behaves.

While having to inject this dependency via method parameter was considered an unjustified burden when we were discussing the mailsender example, in this case the ability to choose the comparer is desirable and actually something the consumer is interested in.

This is therefore a very contextual consideration whether your consumer will be interested in deciding this dependency or not. There is no universal answer here.

5

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật