我如何为一个显然可以有抽象类但我需要一个接口的地方编写一个接口?

发布于 2025-01-13 09:55:02 字数 133 浏览 3 评论 0原文

我有一个抽象类,它由具有非常封闭的实现(私有字段、方法、面向安全)的最终类扩展。 有一个 switch-case 根据某些输入选择要使用的构造函数。 我想摆脱扩展类,但留下一些接口供人们插入自己的实现。 对于如何解决这个设计/OOP 问题有什么建议吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

旧瑾黎汐 2025-01-20 09:55:02

对于“我想制作一个东西,但我希望我们应该制作哪个实际类的概念进行抽象”的通常解决方案是工厂

不幸的是,工厂的名字被那些声称陈词滥调的“哈哈java糟透了——看看所有这些工厂!”的笨蛋玷污了。忽略他们。他们根本没有遇到需要抽象类范围概念(构造函数和静态方法)的情况。因为工厂就是这样的抽象。

简单的示例:

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;
}

您可以使用此方法创建一个从 1 到 10 的链表,或者一个 ArrayList:

List<Integer> iAmAnArrayList = countTo10(ArrayList::new);
List<Integer> iAmALinkedList = countTo10(LinkedList::new);

通过一些泛型魔法,您甚至可以使 countTo10 的返回类型成为您工厂实际创建的类型:

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);

有时我在这些代码片段中使用的 java.util.function.Supplier 是不够的。例如:您想要对某种类型进行抽象,而不仅仅是“让我成为一个”。例如,也许每个实现都附带了在 GUI 中使用的描述,并且工厂既包含创建新方法的方法,也包含返回整个类的描述的方法。这个概念同样容易扩展到“抽象静态方法”。

在这种情况下,请为工厂创建自己的接口或类。

有关安全的重要说明

私有字段、方法、面向安全

听起来你很困惑。 private 对于安全来说绝对没有任何作用。或者,我当然希望它不会:如果您在 JVM 上运行不受信任的代码,相信“它们无法调用私有方法”是一个很大的错误。一般来说,恶意代码可以很好地调用这些代码。 private 是为了传达库/应用程序的未来版本可能会改变它的功能,或者它可能会完全消失。它从来不打算供该源文件之外的任何内容使用,因此,所有相关工具(javac、您的 IDE、构建工具、javadoc 等)都应该表现得好像它不存在一样。此外,对于所有优化目的,该方法实际上都是final,因为它无法被覆盖。

这就是 private 的作用。 不是“增加安全性”。

安全问题的问题是:如果你把它搞砸了,通常你不会有测试或其他系统告诉你这一点。除非您或客户遭到黑客攻击,一切都崩溃了,否则您不会真正知道。您正在指导渗透测试、最佳实践和经验。于是,我想:我分享一些经验,也许会有所帮助。

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.

拔了角的鹿 2025-01-20 09:55:02

如果您想要拥有基类,并且其他用户应该有可能扩展基类的行为。然后我们可以使用策略模式与对象工厂相结合。
让我展示一个 C# 示例(该代码可以轻松转换为 Java,因为我没有使用 C# 的任何特殊功能)。

假设我们想要构建一个计算器。所以我们的基类将是Operation。一些用户希望通过添加新类来添加他们的行为。因此,只要我们保持开放封闭就可以了原则

因此,我们的基类 Operation 将如下所示:

public abstract class Operation
{
    public abstract int Exec(int a, int b);
}

及其具体实现:

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;
    }
}   

然后我们希望工厂能够返回 Operation 类的具体实现实例。如何将具体实现映射到操作?
我们可以在 C# 或 Java Map中使用 Dictionary 。 map = new HashMap(). TKey 将是带有操作名称的枚举:

public enum Operator
{
    Plus,
    Minus,
    Divide,
    Multiply
}

以及将运算符映射到操作的工厂:

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() },
    };
}

以及带有 依赖倒置原理

public class CalculatorWithDependencyInversionPrinciple
{
    public int Exec(Operation operation, int a, int b)
    {
        return operation.Exec(a, b);
    }
}

这个辅助类解决数学问题:

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;
    }
}

我们的代码可以这样运行:

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
            )
        );
}

所以我们创建了简单的类这是可测试的,我们使用策略模式。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文