2

I have a factory class that decides which bank service should be instantiated based on input, so I created it with a switch in a factory class like this:

class BankFactory
{
    public static function build($iban)
    {
        switch ($iban){
            case "123":
                return new BankXService();
            case "456":
                return new BankYService();
        }
    }
}

But every time that I want to add a new bank service, the switch becomes longer and longer.

Is there any better design pattern for this task?

β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
sinak
  • 222
  • 6
  • 19
  • 2
    whatever you're trying to do here, its not actual factory method....factory method something similiar, but its build methods responsibility is provided by a concrete class, which will be specific bankfactory like BankFactoryChina(if u category by country etc...) and BankFactory will be an interface which provides a method build – reyad Jul 31 '20 at 18:36
  • so what's the solution for my case (prevent long switch case)? – sinak Jul 31 '20 at 19:12
  • I've provided solution with explanation....see how to decouple if/else or switch(the code part that needs to be changed) from the client part of your code...hope you understand the goal of Factory Method properly and don't misuse it. Cause, if you misuse design pattern, its become much worse than a bad solution. – reyad Jul 31 '20 at 19:31
  • I know that you've accept [this](https://stackoverflow.com/a/63197932/8864337) as best answer, but from my responsibility I would like to suggest you to avoid the coding style that has been followed in that answer's code snippet. It's very bad style. Please read the comment I have made in the answer. I will not force you, but its good to pick up good coding style as soon as possible. – reyad Jul 31 '20 at 19:50
  • @reyad as on the answer, please explain the reason why it is bad, I am really curious to read this – β.εηοιτ.βε Jul 31 '20 at 20:37
  • @reyad in your code snippet if i have for exampe 100 social bank i have to write 100 if statement in createService method in SocialBank class . the accepted answer is not perfect but in this case seems better if a bank is added i dont have to write another if statement and simply i will add to array – sinak Jul 31 '20 at 20:42
  • I'm sorry that I'm late. But let me explain why and how it is bad?(One comment my not be enough, I may need several) First let me provide two points, OP said: 1. (s)he is using factory class. 2. and the question is tagged design patterns. From the code, I decuced OP is trying to use Factory Method Pattern. I hope you also can see that. Now, first let me explain what are the problems I've found that OP is facing: 1. OP does not providing valid factory method code 2. (s)he wants to replace the if/else or switch with something more suitable – reyad Aug 01 '20 at 17:06
  • My goal to solve OP's problems: 1. first let him show, what's proper factory method. 2. why switch or if/else is not a problem to handle...you can easily handle if/else without any concern when codebase needs to change. – reyad Aug 01 '20 at 17:12
  • In those point I've answered OP's question and I also think that, one SO user should at least guide him/her to understand he is not using proper factory and also give some answer about how to fix the switch problem. – reyad Aug 01 '20 at 17:13
  • The first problem, I've found with @β.εηοιτ.βε answer is that, (s)he is not telling OP that he is not using proper factory method pattern. And the second problem is "dynamic class instantiation" in php. – reyad Aug 01 '20 at 17:15
  • I guess, I don't need to explain the first problem. You can already see that. Now, let's dive into second problem --> the "dynamic class instantiation" in php. why is it bad? – reyad Aug 01 '20 at 17:16
  • 1. first point readability...I guess I don't have to explain it :P. If you ever written any code, it'll be read by others by thousands of times more than you! I hope you get why readability is such a big issue – reyad Aug 01 '20 at 17:17
  • 2. second point code change...now if you're doing a college project, you may only have to code for one bank(And your code may be better than my solution in this case, I'm not quite sure though :P), but in real life you've more possibility of using more banks. So, now what are you going to do solve the issue, Of course, factory method pattern, cause its already been proven by over 30 years of coding experience of millions of coders. But if @β.εηοιτ.βε has a better solution, I would like to hear it, pls share it with us! – reyad Aug 01 '20 at 17:22
  • (2.5). second and a half point...@β.εηοιτ.βε said if there are 100 banks and (also, let me add more complexity) with 100 different services, then what are we going to do? @β.εηοιτ.βε said that, in this case my solution would be shit! Right! Now, let me explain something, If in real life, there are two banks B1 and B2 and they provide same service A, then you'd find out that these two banks takes different approach to provide service A. So, you actually have to write two different methods for service A for two different banks B1 and B2. – reyad Aug 01 '20 at 17:37
  • @reyad you are clearly tiptoeing here, and you might want to read [ask] that will actually explain you why the OP does have a factory class in the services class but did not provide them here because the OP was focusing on the problem at hand, which is "how do I simplify an arm-long switch". You keep on saying dynamic instantiation is bad but still didn't gave any reason as why it is, and in this case it is the best manner, if indeed you have a factory and an interface making all your services respect the same contract – β.εηοιτ.βε Aug 01 '20 at 17:37
  • (2.5) extended: Now, let me tell you why my solution works much better in this case. cause, we just have to implement a service class for each of the service of each bank, its that simple. And all those classes are not going to be in one file, they are going to be divided through namespaces etc. And the if ladder, you would built it through time, not just in one sitting. And deletion is also easy, just delete the corresponding if and you're done. – reyad Aug 01 '20 at 17:42
  • (2.5) extended: the problem with associative mapping(@β.εηοιτ.βε see this) in this solution. There are 100 banks with 100 services, are you really going to map those in an array...really....will it be maintainable and easy to change? – reyad Aug 01 '20 at 17:44
  • @β.εηοιτ.βε I would like to explain more, but if you need summary: 1. readability problem 2. maintainance problem 3. code change problem 4. security problem Now, don't tell me to explain these, cause I won't..I was going to explain and writing all those in comments, It's pretty hard you know. But I guess you don't appreciate it... – reyad Aug 01 '20 at 17:47
  • @β.εηοιτ.βε And I was not answering to OP. I was actually answering those questions you've asked for me. Now, I think, I just wasted time! – reyad Aug 01 '20 at 17:49

2 Answers2

1

What you could do in those kind of cases is to create yourself an association map for the logic, so you will only have to update the associative map each time you add a new bank:

<?php
class BankFactory
{
    private static $bankMap = [
        '123' => BankXService::class,
        '456' => BankYService::class,
    ];
    
    public static function build($iban)
    {
      if(!array_key_exists($iban, self::$bankMap)) {
        throw new InvalidArgumentException(
          'No bank associated with this IBAN pattern'
        );
      }

      return new self::$bankMap[$iban]();
    }
}

class BankXService{}
class BankYService{}

var_dump(BankFactory::build('123'));
var_dump(BankFactory::build('456'));
var_dump(BankFactory::build('789'));

Output:

object(BankXService)#1 (0) { } 

object(BankYService)#1 (0) { } 

Exception: No bank associated with this IBAN pattern
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
  • I am sorry to say this, but this is pretty much abuse of Factory pattern. You shouldn't suggest OP to code like this, even if it helps to avoid some if/else or switch code. You've stored each service instance's name and provide a very bad way to instantiate class...very very bad way..may be one of the worst way I've seen... – reyad Jul 31 '20 at 19:45
  • Then you should provide arguments to your saying, because it seems like a quite important part of the of the PHP community disagree with what you are saying: [symfony](https://github.com/symfony/symfony/blob/de39dbae8f2425f48e790c4f1b3e70f9b702554f/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesTransportFactory.php#L71); [composer](https://github.com/composer/composer/blob/2d3905157d6e0d699446391446ab50f2eef772f2/src/Composer/Repository/RepositoryManager.php#L144) – β.εηοιτ.βε Jul 31 '20 at 20:14
  • [laravel](https://stackoverflow.com/a/47262181/2123530) seems to disagree too, they are using the PHP reflection API, but that just the same. – β.εηοιτ.βε Jul 31 '20 at 20:35
  • I like it :) Even though @reyad is a little over eager, I personally dont like the usage of `$var` as class or method name aswell. But as long as there is no `call_user_func`-like function for classes, we are left with this or ReflectionClass (no thx) or switch/if (no thx times 2). – SirPilan Jul 31 '20 at 22:17
  • @Pilan, there are actual problems of implementing factory like provided code snippet in the answer. And also don't use "dynamic instantiation" in php. It may work well with ORM type problems(if you investgated laravel, symfony etc... frameworks), but it won't provide good solution in most cases. Some problems are: readability, maintainance, security etc...And pls don't ask me to explain, cause I was just shut down! – reyad Aug 01 '20 at 17:54
0

It doesn't matter in what style you code, the if/else or switch would remain in factory method design pattern. But you can choose where to put the if/else or switch, so that, you can control how to handle the change that your code will face in future. First let me explain, how factory works:

The code you've provided is not actual factory method. Now, I am giving a real life bank problem as an example. Let's say there are a lots of Bank in your country which are handled by different groups of people or govt. Let's say some banks are: NationalBank, IndependentBank, SocialBank etc. Now, SocialBank provides a, b services. But IndependentBank provides b, c services and NationalBank provides a, b, x services. I hope you get the idea. How are you going to design BankFactory now(also note, NationalBank's a service and SocialBank's a service are different and so on...).

Here's the solution:

  1. Create a abstract class Bank which will be implemented by every Bank in your country.
  2. Bank abstract class would have a public method named getService() which would build the service and return it for you.
  3. Bank would also have a abstract method called createService() which is responsible for creating the service. Each Bank will implement the method as they want(cause, each banks services are different, even if the service name is same), and this method will hold your if/else or switch or the changes needs to make.

Let's do some code:

<?php
// interface for BankServices
interface BankService {
  public function getServiceName();
  // and other necessary methods
}

// NationalBank services
class NationalBankServiceA implements BankService {
  private $name;
  public function __construct() {
    $this->name = "National Bank Service A";
  }
  public function getServiceName() {
    return $this->name;
  }
}

class NationalBankServiceB implements BankService {
  private $name;
  public function __construct() {
    $this->name = "National Bank Service B";
  }
  public function getServiceName() {
    return $this->name;
  }
}

class NationalBankServiceX implements BankService {
  private $name;
  public function __construct() {
    $this->name = "National Bank Service X";
  }
  public function getServiceName() {
    return $this->name;
  }
}

// SocialBank services
class SocialBankServiceA implements BankService {
  private $name;
  public function __construct() {
    $this->name = "Social Bank Service A";
  }
  public function getServiceName() {
    return $this->name;
  }
}

class SocialBankServiceB implements BankService {
  private $name;
  public function __construct() {
    $this->name = "Social Bank Service B";
  }
  public function getServiceName() {
    return $this->name;
  }
}

// also implement for Independent bank in the same way


abstract class Bank {
  abstract public function createService($serviceType);

  public function getService($serviceType) {
    $bankService = $this->createService($serviceType);
    // execute prepare if necessary...
    // $bankService.prepare();
    // execute other necessary methods
    return $bankService;
  }
}

// now create NationalBank
class NationalBank extends Bank {
  public function createService($serviceType) {
    // here goes your if/else or switch block
    if($serviceType === "a") {
      return new NationalBankServiceA();
    }
    if($serviceType === "b") {
      return new NationalBankServiceB();
    }
    if($serviceType === "x") {
      return new NationalBankServiceX();
    }
    // throw error, cause type not matched
  }
}

// and now create SocialBank
class SocialBank extends Bank {
  public function createService($serviceType) {
    // here goes your if/else or switch block
    if($serviceType === "a") {
      return new SocialBankServiceA();
    }
    if($serviceType === "b") {
      return new SocialBankServiceB();
    }
    // throw error, cause type not matched
  }
}

// in this way also create IndependentBank


// Now, lets see how to use all these codes
$nationalBank = new NationalBank();
$socialBank = new SocialBank();

$nationalBankServiceA = $nationalBank->getService("a");
echo $nationalBankServiceA->getServiceName() . "\n";

$socialBankServiceA = $socialBank->getService("a");
echo $socialBankServiceA->getServiceName() . "\n";

?>

The code is pretty self explanatory. I hope you get the idea of Factory Method, and also why and where we should use it.

reyad
  • 1,392
  • 2
  • 7
  • 12