I have an abstract class that is extended by a final class with very closed implementation(private fields, methods, security oriented). There is a switch-case that depending on some input chooses which constructor to use. I want to get rid of the extended class but leave some interface for people to plugin their own implementation. Any suggestions how to approach this design/OOP problem?
CodePudding user response:
The usual solution to "I want to make a thing, but I want the concept of which actual class we should make to be abstracted" is factories.
The name of factories has unfortunately been besmirched by louts claiming the cliched 'haha java sucks - look at all them factories!'. Ignore them. They simply haven't run into the scenario of needing to abstract class-wide concepts (constructors and static methods). Because factories are exactly that abstraction.
Trivial example:
public List<Integer> countTo10(Supplier<List<Integer>> factory) {
List<Integer> list = factory.supply();
for (int i = 1; i <= 10; i ) list.add(i);
return list;
}
You can use this method to make a linked list with 1 through 10, or an ArrayList:
List<Integer> iAmAnArrayList = countTo10(ArrayList::new);
List<Integer> iAmALinkedList = countTo10(LinkedList::new);
With some generics wizardry, you can even make countTo10
's return type be the actual thing your factory makes:
public <T extends List<Integer>> T countTo10(Supplier<? extends T> factory) {
T list = factory.supply();
for (int i = 1; i <= 10; i ) list.add(i);
return list;
}
// can be used as:
ArrayList<Integer> list = countTo10(ArrayList::new);
Sometimes java.util.function.Supplier
as I used in these snippets is insufficient. For example: You want to abstract more than just 'make me one' about a type. Perhaps each implementation comes with a description for use in GUIs, and the factory contains both a method that makes a new one as well as a method that returns a description for the entire class, for example. The concept extends just as easily to 'abstracting static methods'.
In such cases, make your own interface or class for the factory.
Important note about security
private fields, methods, security oriented
It sounds like you're confused. private
does absolutely nothing for security at all. Or, I surely hope it doesn't: If you run untrusted code on a JVM, trusting that 'they cannot invoke private methods' is a big mistake. Malicious code can invoke those just fine, generally. private
is about communicating that future versions of the library/app may change what this does, or it may disappear entirely. It was never meant for consumption by anything outside of this source file, and as a consequence, all relevant tooling (javac, your IDE, your build tool, javadoc, etcetera) should act as if this does not exist. Also, the method is effectively final
for all optimization purposes as it cannot be overridden.
That's what private
does. not 'add security'.
The problem with security stuff is: If you mess it up, usually you won't have a test or other system that tells you so. You won't really know until all heck breaks loose as you or a customer gets hacked. You're steering on pentests, best practices, and experience. Hence, I thought: I'll share some experience, maybe it will help.
CodePudding user response:
If you want to have base class and other users should have a possibility to extend behavior of base class. Then we can use strategy pattern combined with factory of objects. Let me show an example with C# (this code can be easily converted to Java as I did not use any special features of C#).
Let's say we want to build a calculator. So our base class will be Operation
. Some users wants to add their behavior through adding new class. So it is okay as we keep to Open Closed principle.
So our base class Operation
will look like this:
public abstract class Operation
{
public abstract int Exec(int a, int b);
}
and its concrete implementations:
public class DivideOperation : Operation
{
public override int Exec(int a, int b)
{
return a / b;
}
}
public class MinusOperation : Operation
{
public override int Exec(int a, int b)
{
return a - b;
}
}
public class MultiplyOperation : Operation
{
public override int Exec(int a, int b)
{
return a * b;
}
}
Then we want to have factory that will return instance of concrete implementation of Operation
class. How can we map concrete implementation to operation?
We can use Dictionary<TKey, TValue>
in C#
or in Java Map<TKey, TValue> map = new HashMap<TKey, TValue>()
. TKey
would be enum with operation name:
public enum Operator
{
Plus,
Minus,
Divide,
Multiply
}
and factory which maps operator to operations:
public class OperationToOperator
{
public Dictionary<Operator, Operation> OperationByOperator =
new Dictionary<Operator, Operation>
{
{ Operator.Plus, new SumOperation() },
{ Operator.Minus, new MinusOperation() },
{ Operator.Divide, new DivideOperation() },
{ Operator.Multiply, new MultiplyOperation() },
};
}
and our calculator with Dependency Inversion Principle:
public class CalculatorWithDependencyInversionPrinciple
{
public int Exec(Operation operation, int a, int b)
{
return operation.Exec(a, b);
}
}
And this auxiliary class to solve mathematic problem:
public class Problem
{
public Operator Operator { get; private set; }
public int A { get; private set; }
public int B { get; private set; }
public Problem(Operator operato, int a, int b)
{
Operator = operato;
A = a;
B = b;
}
}
And our code can be run like this:
static void Main(string[] args)
{
List<Problem> conditions = new List<Problem>
{
new Problem(Operator.Plus, 1, 2),
new Problem(Operator.Minus, 4, 3),
new Problem(Operator.Multiply, 5, 6),
new Problem(Operator.Divide, 8, 2),
};
OperationToOperator operationToOperator = new OperationToOperator();
CalculatorWithDependencyInversionPrinciple calculatorWithDependencyInversionPrinciple =
new CalculatorWithDependencyInversionPrinciple();
foreach (Problem condition in conditions)
Console.WriteLine
(
calculatorWithDependencyInversionPrinciple.Exec
(
operationToOperator.OperationByOperator[condition.Operator],
condition.A,
condition.B
)
);
}
So we've created simple classes that are testable and we used Strategy pattern.