Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this a legitimate use of the Abstract Factory pattern?

Here's what I'm trying to implement in my program:

  • The program should open a zip file, which contains many data files
  • The format of the data files can differ between zip files (e.g. csv, tab delimited, or could even be some kind of binary file which needs decoding)
  • However, within a zip file all data files will be of the same type

I have been reading "Design Patterns" by Gamma et al., and have been looking at the Abstract Factory pattern to try to solve this.

Ideally, I want to have one class for the Zip file, which can read any type of data file within it. I guess I would have two classes - FileTypeA and FileTypeB, which could process different formats of data (although there could be more in the future). I would like a way of telling my ZipFile class which type of file to use when reading the data.

So far, this is what I have come up with:

<?php

/**
 * An abstract factory used for creating data files of any type
 */
abstract class DataFileFactory{
    abstract function createFile($id);
}

/**
 * A factory for creating and setting up a data file of type 'A'
 */
class FileAFactory extends DataFileFactory{
    public function createFile($id){
        $file = new FileA();
        $file->setSampleId($id);
        return $file;
    }
}

/**
 * A factory for creating and setting up a data file of type 'B'
 */
class FileBFactory extends DataFileFactory{
    public function createFile($id){
        $file = new FileB();
        $file->setSampleId($id);
        return $file;
    }
}

/**
 * An abstract class which defines some functionality of a data file
 */
abstract class DataFile{
    abstract function readData();
    abstract function setSampleId();
}

/**
 * Concrete class that processes a data file of type 'A'
 */
class FileA extends DataFile{
    public function readData(){
        echo "Reading data from a file A<br/>";
    }

    public function setSampleId(){
        echo "Setting sample id of a file A<br/>";
    }
}

/**
 * Concrete class that processes a data file of type 'B'
 */
class FileB extends DataFile{
    public function readData(){
        echo "Reading data from a file B<br/>";
    }

    public function setSampleId(){
        echo "Setting sample id of a file B<br/>";
    }
}

/**
 * Concrete class that reads a zip file and reads each file within the zip
 */
class ZipFile{
    private $files = array("file1.txt","file2.txt","file3.txt","file4.txt");//this would be an array read from the zip file
    private $sampleId = 1;//this would be derived from some other function

    /**
     * Read all the files in a zip archive.
     * $factory can be an instance of any class that extends DataFileFactory, and is used for creating each file
     */
    public function readFiles(DataFileFactory $factory){
        foreach($this->files as $fileName){//loop through each file in the zip
            $file = $factory->createFile($this->sampleId);//use the factory to create the desired file
            $file->readData();//now read the data from the file!
            echo "object created of type: ".get_class($file)."<hr/>";
        }
    }
}

/***********************************************************************************************
 * IMPLEMENTATION
 ***********************************************************************************************/
$zip = new ZipFile();//create a new zip file
$factory = new FileAFactory();//instantiate a new factory, depending on which type of file you want to create
$zip->readFiles($factory);//read the files, passing the correct factory object to it

Can anyone tell me: (A) Whether this is a good way of achieving what I'm looking for, or is there some simpler way of doing it? (B) Is this actually the Abstract Factory pattern, or have I completely misunderstood?

Thanks in advance!

like image 854
user1578653 Avatar asked Mar 21 '23 12:03

user1578653


1 Answers

It's a good implementation but it can be finetuned a bit if you use interfaces.

An abtract class with all virtual methods it's just a interface so don't use abstract classes, use interfaces.

interface IDataFileFactory{
    public function createFile($id);
}

class FileAFactory implements IDataFileFactory
class FileBFactory implements IDataFileFactory

If you find repeating code in FileAFactory and in FileBFactory methods then it is time to refactor your classes and create inheritance.

interface IDataFileFactory{
    public function createFile($id);
}

abstract class BaseFileFactory implements IDataFileFactory {

//some methods implementation with common features to avoid repeating code
//some abstract methods to be implemented for A and B FileFactories
//absolute abstract base class has no sense because in php you can use interfaces.
//...
}

class FileAFactory extends BaseFileFactory
class FileBFactory extends BaseFileFactory

Then use throug interface:

  public function readFiles(IDataFileFactory $factory){
           //create a file using factory
           return IDataFile; //return Interface implemented by all DataFile types.
        }

You can do the same thing with DataFile base class and so on.

I also recomend to not pass factories in parameters because a factory is out of context. Try to do not mix architecture implementation with data and info process workflow. You can create a container, in a scope accesible for your other classes, to resolve the factory.

The container can, for example, read configuration files to create concrete factory in application bootstrap; read from some value, choosed by the user in previous steps of the user case, stored in a class instance or accepting a parameter in runtime to resolve the factory. It's about implement some kind of simple dependency inyection.

Anyway, this is only my point of view and can be a hurge amount disagreements.

I hope it help.

like image 151
jlvaquero Avatar answered Apr 02 '23 00:04

jlvaquero