9

Status:Fendy 和 Glen Best 的答案同样被我接受和尊重,但既然可以接受并给予赏金,我选择 Fendy 的答案。

Scenario:

如果我有一些代码必须在许多类(很少有明显的微小参数更改)和并发线程中重复使用多次,那么该采用哪种方法?

必须重用的代码可以是任何理智的东西(适当注意静态和非静态上下文以及方法制作技术)。它可以是一种算法,一种执行连接、操作、关闭的数据库方法。任何事物。

  1. 制作一些类似的类MyMethods.class并将所有这些方法放入其中

    1.a. 直接创建方法 static和调用(由所有类和并发线程)MyMethods.someMethod();

    1.b。制作方法 non-static并在调用它们时,instantiate整个类通过MyMethods mm = MyMethods(); mm.someMethod();

  2. 使用https://en.wikipedia.org/wiki/Strategy_pattern中所述的策略模式(此处附有代码)。

  3. 使用https://en.wikipedia.org/wiki/Dependency_injection#Java中所述的依赖注入

Problems:

  1. 有人会说使用这种方法无法进行单元测试 http://en.wikipedia.org/wiki/Unit_testing , 在换出后者时会遇到麻烦。如果你想测试你的类并使用依赖模拟版本

    1.a. 并发调用或多个类会不会有问题?特别JDBC static methods只是一个例子?

    1.b。我认为这会造成太多的内存负载,因为整个类会多次instanticated调用一两个方法

  2. 这超出了我的想象,请解释一下和/或任何优点/缺点

  3. 不想在这个问题的上下文中使用框架.. 这太让我头疼了,请解释一下和/或任何优点/缺点

  4. 等待任何其他策略或建议(如果有)。

Request:请仅在您有经验并深入了解其含义并能够全面地回答时,您的回答才能帮助我和整个社区!

Code:

/** The classes that implement a concrete strategy should implement this.
* The Context class uses this to call the concrete strategy. */
interface Strategy {
    int execute(int a, int b); 
}

/** Implements the algorithm using the strategy interface */
class Add implements Strategy {
    public int execute(int a, int b) {
        System.out.println("Called Add's execute()");
        return a + b;  // Do an addition with a and b
    }
}

class Subtract implements Strategy {
    public int execute(int a, int b) {
        System.out.println("Called Subtract's execute()");
        return a - b;  // Do a subtraction with a and b
    }
}

class Multiply implements Strategy {
    public int execute(int a, int b) {
        System.out.println("Called Multiply's execute()");
        return a * b;   // Do a multiplication with a and b
    }    
}

// Configured with a ConcreteStrategy object and maintains
// a reference to a Strategy object 
class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public int executeStrategy(int a, int b) {
        return this.strategy.execute(a, b);
    }
}

/** Tests the pattern */
class StrategyExample {
    public static void main(String[] args) {
        Context context;

        // Three contexts following different strategies
        context = new Context(new Add());
        int resultA = context.executeStrategy(3,4);

        context = new Context(new Subtract());
        int resultB = context.executeStrategy(3,4);

        context = new Context(new Multiply());
        int resultC = context.executeStrategy(3,4);

        System.out.println("Result A : " + resultA );
        System.out.println("Result B : " + resultB );
        System.out.println("Result C : " + resultC );
    }
}
4

3 回答 3

8

你的问题其实有两层意思。

必须在许多课程中多次重复使用

它可以是设计模式(可重用组件)或内存成本(类实例化)的上下文。从两个不同的角度谈:

内存成本(我对此几乎没有经验,但让我分享一下我的经验)

本节实际上只涵盖了 2 种实例化。

首先是静态的(或组合根中的 DI 实例化)

  • 急切实例化,意味着应用程序启动时将实例化所有类
  • 仅一次实例化

非静态

  • 惰性实例化,意味着类只会在需要时被实例化
  • 每次使用一次实例化

简而言之,如果类很多,静态的成本会很高,如果请求很高(例如在 for 循环中),非静态的成本会很高。但它不应该使您的应用程序变得繁重。不过,java / csharp 中的大多数操作都是创建对象。

类可重用性

1 - 巨型单体代码(一个神级几乎可以做所有事情)

好处:

  1. 易于搜索代码(仍然取决于),你知道每个逻辑都在那里,所以你只需要看看那个大类
  2. 如果它是静态的,你可以在任何地方调用它而不用担心实例化

缺点:

  1. 对一种方法的任何修改都会在其他地方产生错误风险
  2. 违反 SRP,意味着此类可以通过各种原因更改,而不仅仅是一个
  3. 特别是在版本控制中,如果修改发生在单独的分支中,则更难合并,导致代码同步

1a/静态类/单例模式

好处:

  1. 便于使用
  2. 可以在任何地方使用(只需参考和调用,完成)
  3. 不需要实例化对象

缺点:

  1. 难以进行单元测试(难以模拟,后期你会发现准备测试环境需要时间。尤其是数据
  2. 如果有状态(有状态),则在调试期间很难确定当前状态。此外,很难确定哪个函数改变了状态,可以从任何地方改变
  3. 往往有很多参数(可能在 5-11 左右)

关于静态类的一些观点:见这个问题

2 策略模式

实际上这与 3 或 具有相同的设计composition over inheritance

3 依赖注入

好处:

  • 易于模拟和单元测试
  • Must be stateless. 如果类是无状态的,则更容易调试和单元测试
  • 支持重构

缺点:

  • 对于那些不熟悉接口的人来说很难调试(每次你重定向到方法,它都会转到接口的)
  • 创建将导致映射的分层

有状态/无状态

我认为状态在您的应用程序设计中起着重要的作用。通常开发人员会尽量避免在业务逻辑代码中出现状态,例如:

// get data
if(request.IsDraft){
  // step 1
  // step 2
}
else{
  // step 1
  // step 3
}

开发人员倾向于将逻辑放在其他stateless类中,或者至少放在方法中,例如:

// get data
if(request.IsDraft){
    draftRequestHandler.Modify(request);
}
else{
    publishedRequestHandler.Modify(request);
}

它将提供更好的可读性,并且更易于修改和单元测试。也有一种设计模式state pattern or hierarchial state machine pattern,尤其是处理一些这样的情况。

单一职责原则

恕我直言,如果遵循这一原则,则最有益。优点是:

  1. 在版本控制中,更改清楚地说明了哪个类已被修改以及为什么
  2. 在 DI 中,可以连接几个较小的类,从而在使用和单元测试方面创造灵活性
  3. 增加模块化、低耦合、高内聚

TDD(测试驱动开发)

这种设计并不能保证您的代码没有错误。相对于设计阶段的成本时间和分层工作,它具有以下优势:

  1. 易于模拟对象以进行单元测试
  2. 由于单元测试和模块化,更容易重构
  3. 更易于维护/扩展

一些有用的资源

服务定位器反模式

使用装饰器处理横切关注点

我的 2 美分:

使用接口的好处(也适用于组合继承)

做自顶向下设计/DI设计

最后的想法

这些设计和策略并不是决定您的应用程序结构的关键。决定它的仍然是建筑师。我更喜欢遵循一些原则,例如 SOLID、KISS 和 GRASP,而不是决定什么是最好的结构。据说依赖注入遵循了大部分这些原则,但是过多的抽象和不正确的组件设计会导致与单例模式的滥用相同。

于 2013-06-26T12:53:42.347 回答
3

1.a. 将方法设为静态并直接调用(由所有类和并发线程)作为 MyMethods.someMethod();

1.b。使方法成为非静态方法,并在调用它们时通过 MyMethods mm = MyMethods(); 实例化整个类 mm.someMethod();

这两者之间的选择取决于MyMethods.class. 如果MyMethods应该是无状态的,那么这是使用方法的好static方法。否则,如果一个方法调用依赖于另一个方法并MyMethods具有状态(即非最终字段),则使用第二个选项。

使用https://en.wikipedia.org/wiki/Strategy_pattern中所述的策略模式(此处附有代码)。

MyMethods如果要为不同的目的使用不同的类进行扩展,并且根据上下文选择要运行的代码,请使用此模式。正如wiki所说,如果您在运行时之前不知道要使用的算法(取决于某些条件),那么这是要走的路。根据你的规范,MyMethods你没有这样的问题。

使用https://en.wikipedia.org/wiki/Dependency_injection#Java中所述的依赖注入

和上面的回答一样。依赖注入的事情是控制反转。使用的类MyMethods不知道 的实际实现MyMethods,但实际实现的注入被委托给更高级别的权限。它从将要使用的上下文中抽象出外部依赖项。同样,如果MyMethods要成为无状态且恒定的(不打算更改,并且类中方法的行为不依赖于使用它们的上下文),则不需要这些模式,因为这意味着过度工程。

如果逻辑取决于运行它们的上下文,我会得出结论,您应该使用策略DI模式。MyMethods如果这是恒定的(例如,Java 的Math类不关心谁或在什么上下文中调用sqrt()max()或者pow()),那么静态方法就是要走的路。


关于问题:

MyMethods当您使用withstatic方法时,您所说的问题不存在。您将必须测试您的方法是否为特定参数返回正确的值,仅此而已。我不相信测试策略模式Strategy的实际实现或通过依赖注入注入的接口实现会有更多麻烦。可能更难的是测试使用策略的类,因为有时重新创建执行特定策略的上下文并不容易,因为它通常取决于用户输入,但这绝对不是不可能的。就我而言,依赖注入非常适合测试,因为您可以将被测单元与可以轻松模拟的依赖分开。

于 2013-06-26T08:51:40.387 回答
3
  1. 主要问题:代码重用

    如果我有一些代码必须在许多类(很少有明显的微小参数更改)和并发线程中重复使用多次,那么该采用哪种方法?

    因为您没有考虑任何剪切和粘贴,所以我认为您的意思是:

    ...被许多班级多次重复使用...

    你所问的没有什么特别或具体的。在单个应用程序内或跨多个应用程序重用代码是很常见的。常见的答案:使用面向对象的设计/编程。将代码放在一个类中,创建一个对象作为实例,调用该对象...

    1a。通过静态方法重用:

    将方法设为静态并直接调用(由所有类和并发线程)作为 MyMethods.someMethod()

    • 如果你的类是无状态的(没有实例变量),这是一个很好的方法。
    • 如果您的类具有类级状态(仅限静态实例变量),但变量是只读的(不可变的),这是一个好方法。
    • 如果您的类具有类级状态(仅限静态实例变量)并且变量更改值(可变),那么这可能是一种合适的方法。但是,如果您希望您的类可以从多个线程访问,则必须使其成为线程安全的:使您的方法同步,或者让内部代码同步(互斥线程访问)以用于所有数据读取和写入。
    • 如果您的代码具有对象级状态(非静态实例变量),则此方法将不起作用 - 不实例化对象就无法访问非静态实例变量。

    1b。通过对象实例化的非静态方法重用:

    使方法成为非静态方法,并在调用它们时通过 MyMethods mm = MyMethods(); 实例化整个类 mm.someMethod();

    • 如果你的类只有静态实例变量,这是一个糟糕的方法,因为实例化对象什么都没有
    • 如果您的类具有非静态实例变量 - 这是唯一的方法。强制实例化对象以访问变量。
    • 如果要跨多个线程使用实例化对象,它们应该是(按优先顺序):
      • 无状态(无实例变量) - 真正适用于 1a - 无需实例化
      • 不可变(只读非静态实例变量)
      • 在所有数据读取和写入上同步
  2. 使用策略模式

    策略模式可以是很好的实践。但这与您的整体问题无关。策略模式用于特定原因 - 在不影响调用者的情况下“即时”交换算法/处理逻辑的实现。

  3. 使用依赖注入

    使用依赖注入的原因如下:

    • 工厂和对象缓存功能:从您的代码中删除对象创建、缓存和查找的责任
    • 对象共享的中介:允许各种类共享同一个对象实例(存储在给定的范围/上下文中),而不需要两个类直接在它们之间传递对象
    • 对象实例之间的“连线控制”——建立对象关联,在CDI下,支持拦截器、装饰器和观察者模式

    如果使用得当,这可能是非常好的做法。在您的情况下,这只能适用于选项 1b。依赖注入是关于对象实例化和提供给变量的。

问题:

  1. 有人会说单元测试是不可能的

    • 模拟框架(和手工编码的单元测试)一直在处理用模拟逻辑替换类。这是一个非常正常的情况。你可以扩展一个类来模拟它的逻辑——如果它没有最终的公共方法。此外,您可以将方法声明转移到接口,让类实现接口,然后通过使用不同的类实现接口来模拟。
    • 换句话说,这不是影响您的任何选项的约束/力

    1a。往上看

    1b。内存负载

    我认为这会造成太多的内存负载,因为整个类会被实例化很多次,只是为了调用一两个方法

    一个小问题。根据每个对象实例中的数据(实例变量),每个对象实例可能小至十几个字节或大至兆字节——但通常向低端倾斜(通常 < 1kB)。每次实例化类时,不会复制类代码本身的内存消耗。

    当然,根据您的要求最小化对象的体积是一种很好的做法 - 如果您已经有一个可用的实例,请不要创建新实例。创建更少的对象实例并在您的应用程序中共享它们 - 将它们传递给构造函数方法和设置方法。依赖注入是一种“自动”共享对象实例而不将它们传递给构造函数/设置器的好方法。

  2. 往上看

  3. 往上看

于 2013-07-04T05:06:51.307 回答