Don Cheadle
Don Cheadle

Reputation: 5576

Enforce method execution order in Abstract Class

I have a business case in which 3 things need to happen, in sequence:

  1. download()
  2. process()
  3. upload()

Now, the Abstract Class FileTransfer provides implementation for 1. downloadFiles() and 3. upload(), but not 2. process() -- child classes (like MusicFileTransfer or VideoFileTransfer or PDFFileTransfer) will do different things in the 2. process() stage.

So it seems clear to make the abstract class like so:

public abstract class FileTransfer {

public void download() {
    // implementation provided
}

public abstract void process(); // implementation not provided

public void upload() {
    // implementation provided
}

}

But one issue -- There will never ever be a time in which it is okay for, say, MusicFileTransfer to call process() before download() or any other order. The process must always be 1. download(), 2. process(), then 3. upload().

So I imagine something like:

public void doTransfer() {
    // private methods since we want to enforce this order of execution
    download(); // implem provided
    process(); // abstract method
    upload(); // implem provided
}

in FileTransfer to wrap around these three calls. But in order for the child class MusicFileTransfer to override process() ... it must be public or protected (not private).

What should I do to work around this pickle? Have a public doTransfer() and a public process() and just make sure process() is never ever called? Or do away with the doTransfer() and hope that the order is always correct?

Upvotes: 2

Views: 2117

Answers (5)

robert
robert

Reputation: 4867

The typical approach here is to keep track of the state, so that if one of these methods is called out of order you would then throw IllegalStateException - however if your abstract method process is where this logic is implemented then of course you won't have this kind of control.

final void process() {
    if (state != DOWNLOAD) throw new IllegalStateException("Download must be invoked first");
    ...

A possible solution to this is to take an approach similar to that of java.lang.Thread where the caller calls start and the implementer overrides run. You could then expose lets say doProcess as your abstract method, which is invoked when your caller calls process:

void process() { // from base-class
    if (state != DOWNLOAD) throw new IllegalStateException("Download must be invoked first");
    doProcess();
}

final void doProcess() { // implementing abstract method
    // implementation logic here

OR, what may be the neatest for your client code, though perhaps requiring a bit more boiler-plate from you, would be to use 'delegation': Subclass your original base-class, to a delegator which maintains resposibility for invoking your implementation class:

class FileTransferDelegator extends FileTransfer {

    final Filetransfer delegate;

    FileTransferDelegator(Filetransfer delegate) {
        this.delegate = delegate;
    }

    void process() {
        if (state != DOWNLOAD) throw new IllegalStateException("Download must be invoked first");
        delegate.process();
    }

Upvotes: 0

mdewitt
mdewitt

Reputation: 2534

If all the classes that extend FileTransfer will be in the same package, you can make download(), upload(), and process() protected methods. That way you are limiting who can call them.

Then keep your template method, doTransfer() public so that is the only method that can be called by outside classes (outside of the package)

I would also add a javadoc comment specifying that only doTransfer() should be invoked.

Like this:

public abstract class FileTransfer {

    /**
     * Handles the file transfer process
     */
    public void doTransfer() {
        // private methods since we want to enforce this order of execution
        download(); // implem provided
        process(); // abstract method
        upload(); // implem provided
    }

    /**
     * Invoking outside of doTransfer can cause unexpected behavior.
     */
    // If you never want subclasses to override this, make it private
    protected void download() {
        // implementation provided
    }

    /**
     * Invoking outside of doTransfer can cause unexpected behavior.
     */
    // If you never want subclasses to override this, make it private
    public void upload() {
        // implementation provided
    } 

    /**
     * Invoking outside of doTransfer can cause unexpected behavior.
     */
    // Only the subclasses in the same package will be able to access this
    // for implementation purposes
    protected abstract void process(); // implementation not provided
}

Your API for the method to others using your class will just be

FileTransfer
    void doTransfer()  
        Handles transfering the files etc...

None of the other methods should be available from the API.

Note: It will however, still possible for your subclasses that are implementing process() to invoke it. There is no way from completely preventing them from doing that. The best you can do is add documentation specifying that it is not encouraged.

The other problem is that because you don't know how the subclasses are actually implementing process() you can't say for certain that it can't be called alone. You are only assuming that they are implementing it in a way you expect them to that would cause it to behave incorrectly.

Upvotes: 1

cogitos
cogitos

Reputation: 336

In this case you might consider making the process() method protected, meaning that subclasses can extend it, but consumers may not call it directly. Additionally, perhaps download() and upload() methods shouldn't be public either. Consider the the following:

public abstract class FileTransfer {

    private void download() {
        // implementation provided
    }

    protected abstract void process(); // implementation not provided

    private void upload() {
        // implementation provided
    }

    public void doTransfer() {
        // private methods since we want to enforce this order of execution
        download(); // implem provided
        process(); // abstract method
        upload(); // implem provided
    }

}

Sample implementation class:

public class FTPFileTransfer {

    protected void process() {
        // implement FTP file transfer here

    }
}

Sample consumer method:

public void consumerMethod() {
    FileTransfer fileTransfer = factory.getFTPFileTransfer();
    fileTransfer.doTransfer();
} 

Upvotes: 1

rgettman
rgettman

Reputation: 178263

You need the template method pattern. Create a doTransfer method (final) in the FileTransfer class that defines the order of the methods that needs to be called. Make the download and upload methods also final so they can't be overridden. The process method is the abstract method; make it protected so it can't be called from other classes. It can't be public, or else it can be called by some other class out of order.

public final void doTransfer() {
    // protected methods since we want to enforce this order of execution
    download(); // implem provided
    process(); // abstract method
    upload(); // implem provided
}
protected final void download() {
    // implementation provided
}

protected abstract void process(); // implementation not provided

protected final void upload() {
    // implementation provided
}

Then subclasses can only implement the process method and they can't override any other methods.

Upvotes: 7

KSK
KSK

Reputation: 158

Can't the process() method be protected?

Upvotes: 0

Related Questions