5

Edit: I've realised that this pattern feels a lot like currying, a technique that functional programmers use to specify function parameters in advance of invocation. The difference here is that we're currying constructors on objects instead of simply currying functions.


Throughout a couple of projects I've found myself using this bizarre design pattern that I can't find the name of. Does it have a name? Perhaps it's just bad practice, you tell me.

The Design Pattern

With this pattern, you would have...

  1. An abstract base class without abstract methods (we can discuss this later).
  2. Many "implementations" of the base class. However, these implementations would only serve to invoke the constructor of the base class.

A Java Example (with hypothetical scenario)

I will define a hypothetical scenario to provide some context.

Scenario:

Bob is writing a small API for scanning source code. He wants to be able to check if a comment starts/ends at a given index within the source code.

Here is Bob's code.

1. The Abstract Base Class

public abstract class CommentDetector {

    private final String startPattern;
    private final String endPattern;

    protected CommentDetector(String startPattern, String endPattern) {
        this.startPattern = startPattern;
        this.endPattern = endPattern;
    }

    public boolean commentStartsAt(int index, String sourceCode) {
        // ...
    }

    public boolean commentEndsAt(int index, String sourceCode) {
        // ...
    }

}

You may be wondering why it is abstract but has no abstract methods. This is simply because Bob doesn't want you to instantiate it directly. Bob wants you to write an implementation of CommentDetector and then instantiate that instead. Here are two of Bob's implementations...

2. Some Implementations

One for multi-line comments in Java:

public class JavaMultiLineCommentDetector extends CommentDetector {

    public JavaMultiLineCommentDetector() {
        super("/*", "*/");
    }
}

One for single-line comments in Java:

public class JavaSingleLineCommentDetector extends CommentDetector {

    public JavaSingleLineCommentDetector() {
        super("//", "\n");
    }
}

Bob has written these implementations for us so that we can write new JavaMultiLineCommentDetector() instead of new CommentDetector("/*", "*/").

Bob also encourages you to write your own implementations for other languages if need be.


Summary

  • It feels like the intent of this design pattern is to improve readability of the code by pre-defining constructor calls.

  • This design pattern gives a polymorphic feel to the code (even though it may/may not be truly polymorphic).

  • Writing new implementations is quick and easy.

  • Implementations do not depend on each other, and can be compiled/deployed independently.

Does this design pattern have a name?

byxor
  • 5,930
  • 4
  • 27
  • 44

3 Answers3

1

It feels like the intent of this design pattern is to improve readability of the code by pre-defining constructor calls.

In OOP, constructors should not be used to define contract since :

  • it is less precise than a abstract method
  • concrete classes may misused the super constructor
  • it is not designed for being extensible in a OOP way since it doesn't allow the overriding of behavior.

Generally, the Factory method or the builder Design Patterns are more commonly used when you want allow to your class clients to choose what object must be used in a processing :

Abstract class :

public abstract class CommentDetector {

  private final String startPattern;
  private final String endPattern;

  public abstract String getStartPattern();
  public abstract String getEndPattern();

  public boolean commentStartsAt(int index, String sourceCode){        
      getStartPattern()...
  }

  public boolean commentEndsAt(int index, String sourceCode){
      getEndPattern()....
}

Concrete class

public class JavaSingleLineCommentDetector extends CommentDetector {

     public String getStartPattern(){
         return "//";
     }

     public abstract String getEndPattern(){
         return "\n";
     }
}

This design pattern gives a polymorphic feel to the code (even though it may/may not be truly polymorphic).

Writing new implementations is quick and easy.

It's true in this case since the class becomes extensible. You can use a concrete class and override any factory method if you want.

Community
  • 1
  • 1
davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • This is very good. But is there a way to restrict the visibility of `getStartPattern()` and `getEndPattern()`? – byxor Aug 22 '16 at 13:44
  • 1
    Thank you :) No since it is which allows to client classes to define their own implementation (or reusing a existing implementation to override id). If you want to allow extensibility, you must allow clients public contract – davidxxx Aug 22 '16 at 13:45
  • 1
    IMO it's a stretch to call this an example of the factory-method pattern... – Oliver Charlesworth Aug 22 '16 at 13:46
  • @Oliver Charlesworth Sorry I understood. You are right since the created object is a String. It's rather a builder. No ? – davidxxx Aug 22 '16 at 13:49
1

With the extensible enum pattern (already noted in comments) you can also avoid the inheritance:

public interface CommentDelimiter {
    String getStartPattern();
    String getEndPattern();
}

public interface CommentDetector {
    boolean commentStartsAt(int index, String sourceCode);
    boolean commentEndsAt(int index, String sourceCode);
}

public enum CommentDetectors implements CommentDetector {
    JAVA_MULTILINE(CommentDelimiters.JAVA_MULTILINE),
    JAVA_SINGLELINE(CommentDelimiters.JAVA_SINGLELINE);

    // ... store commentDelimiter

    public boolean commentStartsAt(int index, String sourceCode) {
        // ... using commentDelimiter.getStartPattern()
    }

    public boolean commentEndsAt(int index, String sourceCode) {
        // ... using commentDelimiter.getEndPattern()
    }
}

public enum CommentDelimiters implements CommentDelimiter {
    JAVA_MULTILINE("/*", "*/"),
    JAVA_SINGLELINE("//", "\n");

    // ... store start, end
}
charlie
  • 1,478
  • 10
  • 20
  • I like the way you've cleanly split up the delimiters and detectors here. This was something I hadn't thought of. I'd go further as to split the `CommentDelimiters` and `CommentDetectors` into multiple files for each language. – byxor Aug 23 '16 at 08:20
0

What is wrong with Factory Method pattern?

public class CommentDetector {
    //...

    private CommentDetector(String startPattern, String endPattern) {
        this.startPattern = startPattern;
        this.endPattern = endPattern;
    }

    public static CommentDetector giveMeThisInstance() {
        return new CommentDetector("//", "\n");
    }
    public static CommentDetector giveMeThatInstance() {
        return new CommentDetector("/*", "*/");
    }    
}

This approach will save your permgen memory. And for my subjective view is more consistent: keep everything as single class, but create objects with different properties.

Community
  • 1
  • 1
Rudziankoŭ
  • 10,681
  • 20
  • 92
  • 192
  • This seems like one of the better approaches. However it would pollute the original CommentDetector class if I wanted to add more languages. – byxor Aug 22 '16 at 13:42
  • 2
    @BrandonIbbotson You can make the Constructor protected and then place the Factory in another class or even multiple other classes. – Polygnome Aug 22 '16 at 13:43
  • @Polygnome Good point. That approach gives us the all the benefits of my original idea. – byxor Aug 22 '16 at 13:48
  • 2
    @Rudziankoŭ this is not the [FactoryMethod pattern](https://en.wikipedia.org/wiki/Factory_method_pattern) but is nonetheless a good option. You could also convey the flavor of comment detector through the variable name : `CommentDetector javaSingleLineDetector = new CommentDetector("//", "\n");` – guillaume31 Aug 22 '16 at 13:52
  • @guillaume31 That is true too, however that requires that you re-write the String arguments each time which could lead to error or too much repetition. – byxor Aug 22 '16 at 13:54
  • @Rudziankoŭ Good implementation but Keeping everything in a single class doesn't favor the extensibility for class clients. since they must modify `CommentDetector` to add new behavior and so must have the right to modify it. – davidxxx Aug 22 '16 at 13:56
  • 3
    @BrandonIbbotson, create classes just in order to call `super();` is kind of repetition too, isn't it? – Rudziankoŭ Aug 22 '16 at 13:57
  • @Rudziankoŭ You're right. I used this pattern in kotlin where constructor calls could be made in the class declaration. It reduced a lot of repetition in that case. It really depends on the language syntax and also how frequently the classes are being instantiated. It's hard to say. – byxor Aug 22 '16 at 14:01