Consider this interface
public interface IDoSomething {
void DoAAA();
void DoBBB();
void DoCCC();
}
and these two implementations
public class MyObject1 implements IDoSomething {
public MyObject(int a, xObjx x)
..
}
public class MyObject2 implements IDoSomething {
public MyObject(int a, xObjx x)
..
}
How can I not expose the implementation classes to other developers? Specifically how can I prevent the following?
IDoSomething C = new MyObject2();
Is using a factory class the best practice?
public class DoSomethingFactory {
static IDoSomething getDoSomething(int a, xObjx x) {
...
}
}
How can I extend this pattern to include implementations that have different constructors?
If you're always going to be constructing the MyObject1/2 instances in your own code, you might be able to make them package private (default permissions). A "builder" pattern might make sense too.
Can you maybe elaborate on how / why it matters if the constructors of the objects are exposed? Interfaces just define functionality requirements, not construction requirements.
Good software is written on a need to know
basis. The more ignorant a component is about how its companion classes get the job done the better. That is purpose that interfaces serve. They allow willfully ignorant programmers to write code on a need to know
basis.
One problem is a programmer that wants to know
can almost always know
. Even if you could prevent the assignment which you can't, they can cast the obfuscated object into its implementation, inspect its properties and methods with reflection, or pipe the jar into a hex editor. This isn't really something you can stop.
What you can do is write code that makes the lives of need to know
programmers easier, clearly communicates the intention of your design, and reduces the temptation for programmers to cheat(using dirty implementation details as a crutch). The best way accomplish this is to use Creational Patterns.
In reference to your point about constructors being different this is handled nicely by all the basic creational patterns. Because they provide an abstraction over the constructor. You get to decide how much or how little detail to expose. Maybe you have a constructor like the one below:
public ColorThing(int r ,int g, int b)
that accepts numeric color values. In your implementation of a creational pattern you might decide to simplify this by making the constructor private
and only allowing access to a static factory method.
private ColorThing(int r , int g, int b){
...
}
public static ColorThing buildRedColorThing(){
return new ColorThing(100,0,0);
}
This would limit consumers of your ColorThing
class to only being able to construct red
things.
Listed below are several basic creational patterns that sort of do what you want. They provide varying degrees of obfuscation building up to the Abstract Factory Pattern
which yields the highest degree of encapsulation but also requires the most boiler plate.
Static Factory Method
This is the guy from the ColorThing
example above. This simply provides a public static method on the class that protects the nitty gritty details of the constructor. Great for simplifying access to a constructor
with lots of parameters. In this case, you can make your constructor private
and provide as many static factory methods as you need.
public class MyObject1 implements IDoSomething {
private int prop a;
public static IDoSomething getAnObject(int a){
return new MyObject(a, new xObjx());
}
private MyObject(int a, xObjx x)
...
}
The Builder Pattern
The builder pattern has an accompanying class responsible for creating your class. It uses java's scope rules to ensure that the constructor is accessed in a secure fashion. This is also very useful if you want to have a lot of telescoping constructor args because you can rely on dot chaining
of the methods of the builder class (new MyObject1.MyObjectBuilder().setA(2).build()
)
public class MyObject1 implements IDoSomething {
private int prop a;
private MyObject1(int a, xObjx x)
...
public class MyObjectBuilder {
private int a;
public MyObjectBuilder setA(int a){
this.a = a;
return this;
}
public IDoSomething build(){
return new MyObject1(a, new xObjx);
}
}
}
Abstract Factory
The Abstract Factory Pattern
allows you to completely encapsulate the construction of a class. This will only every expose the interface, but it also has the most boiler plate because you have to create a Factory
and a set of Product
implementations and interfaces for each family of classes you want to manufacture. Here is a text book example of how the classes in this pattern relate. This pattern is highly extensible and you will see it used to great effect in lots of library code. This is because the designers of these libraries commonly don't know the full set of implementations their framework code will need to support. By using an Abstract Factory Pattern
they can let future developers build on their code while limiting the restrictions imposed by their assumptions.
Which one you pick will be entirely contingent on your circumstances, but you should start with the simplest one that satisfies your needs and work your way to the more complex ones.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With