3

I am learning about coding to an interface in OOP programming. I understand you should code to an interface so you can 'swap out' functionality.

However, I have a situation where I feel I can reuse an interface again rather than simply swap it out.

interface GetFile
{
    public function getFile();
}

class GetFileFromUrl implements GetFile
{
    public function __construct($url, $destination_path) {...}

    public function getFile()
    {
        // download file from url and return path of file

        return 'file_path';
    }
}

class GetFileFromZip implements GetFile
{
    public function __construct($path, $extraction_path) {...}

    public function getFile()
    {
        // extract file from zip and return path of file

        return 'file_path';
    }
}

class DoStuff
{
    // coding to an interface
    public function doStuff(File $file) {
        $do_stuff = $file->getFile();
        //do stuff with file
    }
}

I thought this would allow the greatest amount of flexibility in code reuse in future as I can extend the interface further

class GetFileFromDirectory implements GetFile(...)
class GetFileFromTar implements GetFile(...)

I feel this a fairly good approach as I can pass whatever implementation of GetFile to DoStuff and it will execute readFile within the doStuff() method.

My question I am trying to ask, is how can I 'chain' these classes which implement GetFile together?

i.e. GetFileFromUrl then GetFileFromZip to get a zip file from a URL and unzip it?

So using just one would be

$file_from_url = new GetFileFromUrl($url_path, $destination_path);
$do_stuff      = (new DoStuff)->doStuff($file_from_url);

But 'chaining' two I am currently doing;

$zip_file_path = (new GetFileFromUrl($url_path, $destination_path))->getFile();
$file_from_zip = new GetFileFromZip($zip_file_path, $destination_path);
$do_stuff      = (new DoStuff)->doStuff($zip_from_tar);

Is there a better / cleaner way of doing this?

Thanks to thepacker, my revised thought is to separate the functionality into one interface which gets a file from somewhere and another interface which does something to the file;

interface GetFile()
interface ConvertFile()

Then I could do

class GetFileFromUrl implements GetFile {...}

class ConvertFileFromZip implements ConvertFile {
    // coding to an interface
    public function __construct(GetFile $from) {...}
}

$zip_from_url = new GetFileFromUrl($url, $destination);
$file_path    = new ConvertFileFromZip($zip_from_url);

I just want to confirm this is the correct course of action in this case.

4

Well maybe I can enlighten you slightly. You are not swapping the interface out, but the implementation.

GetFile fileGetter=new GetFileFromUrl(...);
// but you can use every other filegetter instead... 
fileGetter->getFile();

Think about this class:

class ConfigParser 
{
    public function readConfig(GetFile fileGetter)
    {
        ...
        currentFile = fileGetter->getFile()
        ...
    }
}  

Whenever you feel that you need something to get files from anywhere, you can implement a method and transfer the Strategy on how to get files either from Zip or tar, or URL or whatever strategy you like to the one instance which requires that. Consider this:

ConfigParser configParser...;
configParser->readConfig(fileGetter)

Since ConfigParser doesn't care about how to access files (it uses the interface instead), it can be used to read and parse configfiles from everywhere.

You can also implemet a Class using another instance of GetFile. Like a FileGetter which uses urls, but can cache files.

class CachingFileGetter implements GetFile {
  GetFile wrappedGetFileStrategy;
  ....

  public function __construct(GetFile nonCached) {
      wrappedGetFileStrategy=nonCached;
      ....
    }

  public function getFile() {
    if(isCached())
    {
       [do your caching magic here]
    }
    else
    {
       wrappedGetFileStrategy->getFile();
       ...
    }
  }
}

This is what interfaces could be for. You are separating your concerns like implementing a cached FileGetter, bou you do not care where that file comes from - Instead you are using the abstract GetFile-thing. Read about Liskov-Substitition-Principle the L in SOLID.

[Update]

In response to your update. Do not use Method chaining (allo over your code) since you have a way to hide that behavior. You can replace the idea of a cache simply by the idea of a virtual file system (VFS), so you do not need to copy everywhere:

$zip_file_path = (new GetFileFromUrl($url_path, $destination_path))->getFile();
$file_from_zip = new GetFileFromZip($zip_file_path, $destination_path);

The Idea behind that is, that your code doesn't need to know that the file is a zip in an url, it is just a file resource. There is only one place to leave that information, that you are using Zip files over URLs, in the configuration. Do not copy and paste such "solutions" (knowledge about your configuration) all over your code - If you change your mind to use newest FOO istead of zip, or BAR instead of URLs you are constantly fixing your code everywhere. Implement a class VFS which is hiding that method chaining behavior and uses URL-FileGet and Zip-FileGet inside. And then use

(new DoStuff)->doStuff($VFS) instead .
  • Thank you for your answer. I have updated my question in light of your response to better articulate my question. – myol Oct 4 '15 at 6:31
  • Thank you for clarifying my second revision. So the VFS class should implement the GetFile interface too? And I shouldn't split the functionality into GetFile and ConvertFile? In a way the VFS class could be called GetFileAsZipFromUrl right? – myol Oct 4 '15 at 7:44
  • 1
    Yes VFS should implement the GetFile-Interface too, since it provides Files or FileRessources or FileAccess. But VFS can be smarter and decide what part belongs to "URL" and what part to the zip-Container. It could do use a Factory-Class to create then a file Getter for the URL and one for the zip-Container. Always try to separate all your concerns - it will you to good designs. – thepacker Oct 4 '15 at 7:51
  • So although I have the concerns separated using different classes implementing GetFile, does the VFS / GetFileAsZipFromUrl class still ad-hear to SoC because it seems like it is basically joining separated functionality? I thought this would be the responsibility of some sort of Handler class using a different interface – myol Oct 4 '15 at 8:06
  • 1
    And "no" to getFileAsZip from URL just hide that in getFile(). Convert File is another concern and should be treated separately. – thepacker Oct 4 '15 at 8:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.