第一条:考虑使用静态方法替代构造器

The traditional way for a class to allow a client to obtain an instance is to provide a public consructor.There is another technique that should be a part of every programmer's toolkit.A class can provide a public static factory method,which is simply a static method that returns an instance of the class.Here's a simple example from Boolean(the boxed primitive class for boolean).This method translates a boolean primitive value into a Boolean object reference:

    public static Boolean valueOf(boolean b) {

        return b ? Boolean.TRUE : Boolean.False;

    }

Note that a static factory method is not the same as the Factory Method pattern from Design Patterns[Gramma95].The static factory method described in this item has no direct equivalent in Design Patterns.

对于一个类来说,允许获取一个对象实例的传统的方式就是提供一个公有的构造器。还有一个另外的方法应该出现在每一个程序员的工具包中。一个类可以提供一个简单的公有静态工厂方法,返回一个类的实例。Boolean类(基本类型boolean的包装类)中有个简单的例子,下面这个方法将一个boolean值转换成了Boolean对象的引用:

    public static Boolean valueOf(boolean b) {

        return b ? Boolean.TRUE : Boolean.False;

    }

注意这里的静态工厂方法并不是设计模式中的工厂模式。本条中描述的静态工厂方法在设计模式中并没有直接的等价物。

A class can provide its clients with static factory methods instead of,or in addition to,public constructors.Providing a static factory method instead of a public constructor has both advantages and disadvantages.

类除了给客户端提供公有构造方法外还可以提供静态工厂方法代替。提供静态工厂方法替代公有构造器有优点也有缺点。

One advantage of static factory method is that,unlike constructors,they have names.If the parameters to a constructor do not,in and of themselves,describe the object being returned,a static factory with a well-chosen name is easier to use the resulting client code easier to read.For example,the constructor BigInteger(int,int,Random),which returns a BigInteger that is probably prime,would have been better expressed as a static factory method named BigInteger.probablePrime.(This method was added in Java 4.)

静态工厂方法的第一个优点是,不像构造器那样,他们有名字。如果构造器的参数没有描述返回值对象,那么一个具有好的名字的静态工厂方法将会使代码更加易读。例如,构造方法BigInteger(int,int,Random),返回一个可能为质数的BigInteger,相比较而言,静态工厂方法BigInteger.probablePrime(Java 4中增加的方法)可能表达的更好一点。

A class can have only a single constructor with a given signature.Programmers have been known to get around this restriction by providing two constructors whose parameter lists differ only in the order of their parameter types.this is really bad idea.The user of such an API will never be able to remember which constuctor is which and will end up calling the wrong one by mistake.People reading code that uses these constructors will not know what the code does without referring to the class documentation.

一个类对于给定的签名只能提供一个构造器。程序员知道可以提供两个构造器来绕开这个限制,通过改变参数类型的顺序,这真的是个坏主意。API文档的使用者将不可能记住哪个构造器做什么的,有可能会发生错误的调用。读代码的人如果不看文档将不会知道构造器的作用。

Because they have names,static factory methods don't share the restriction discussed int the previous paragraph.In cases where a class seems to require multiple constructors with the same signature,replace the constructors with static factory methods and carefully chosen names to highlight their differences.

因为有名字,所以静态工厂方法不存在上段所讨论的限制。如果一个类中需要多个相同签名的构造函数,那么使用静态工厂方法替代他们,并认真的选择方法名来强调他们的不同。

A second advantage of static factory methods is that,unlike constructors,they are not required to create a new object each time they're invoked.This allows immutable classes to use preconstructed instances,or to cache instances as they're constructed,and dispense them repeatedly to avoid creating unnecessary duplicate objects.The Boolean.valueOf(boolean) method illustrates this technique: it never creates an object. This technique is similar to the Flyweight pattern. It can greatly improve performance if equivalent objects are requested often,especially if they are expensive to create.

静态工厂方法的第二个优点是,不像构造器那样,他们不需要在每次被调用的时候都创建一个新的对象。这允许了不可变类去使用之前构造的实例,或者缓存创建过的实例,然后反复的发放它们来避免创建多余的对象。Boolean.valueOf(boolean)方法表明了这个技巧,他从不会创建一个对象。这个技巧和设计模式中的享元模式相似。如果一个对象经常被请求的话,这种方式可以提高性能,特别是创建对象的代价很高时。

The ability of static factory methods to return the same object from repeated invocations allows classes to maintain strict control over what instances exist at any time.Classes that do this are said to be instance-controlled classes.There are several reasons to wirte instance-controlled classes.Instance contro allowsa a class to guarantee that it is a singleton or noninstantiable.Also,it allows an immutable value class to make the guarantee that no two equal instances exist: a.equals(b) if and only if a == b.This is the basis of the Flyweight pattern.Enum types provide this guarantee.

静态工厂方法从重复调用返回同一对象的能力允许类在任何时候保持对存在的实例的严格控制。可以做到这点的类被称为实例控制类。这里有几个要写实例控制类的原因。实例控制允许一个类去保证它的单例或者不可实例化。同样,它还允许不可变值类去保证没有两个相同实例的存在:当且仅当a==b时,a.equals(b)。这是享元模式的基础。枚举类型保证了这一点。

A third advantage of static factory methods is that,unlike constructors,they can return an object of any subtype of their return type.This gives you great flexibility in choosing the class of the returned object.

静态工厂方法的第三个好处是,他们可以返回一个返回值类型的任意一个子类型。这让你在选择返回对象时具有非常大的灵活性。

One application of this flexibility is that an API can return objects without making their classes public.Hiding implementation classes in this fashion leads to a very compact API.This technique lends itself to interface-based frameworks,where interfaces provide natural return types for static factory methods.

这种灵活性的一个应用就是API可以在不公开实现类信息的情况下返回对象。隐藏实现类的方式会使API非常的紧凑。这种技术衍生了很多基于接口的框架,接口为静态工厂方法提供原始返回类型。

Prior to Java 8,interfaces couldn't have static methods.By convention,static factory methods for an interface named Type were put in a noninstantiable companion class names Types.For example,The Java collections Framework has forty-five utility implementations of its interfaces,providing unmodifiable collections,synchronized collections,and the like.Nearly all of these implmentations are exported via static factory in one noninstantiable class(java.util.Collections).The classes of the returned objects are all nonpublic.

Java 8之前,接口不可以有静态方法。因此按照惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化的伴生类中。例如,Java Collections Framework 的集合接口有45个工具实现,分别提供了不了修改的集合、同步集合,等等。几乎所有这些实现都通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。所有返回对象的类都是非公有的。

The collections Framework API is much smaller than it would have been had it exported forty-five separate public classes,one for each convenience implementation.It is not just the bulk of the API that is reduced but the conceptual weight: the number and  difficulty of the concepts that programmers must master in order to use the API.The programmer knows that the returned objects has precisely the API specified by its interface,so there is no need to read additional class documentation for the implementation class.Furthermore,using such a static factory method requires the client to refer to the returned object by interface rather than implementation class, which is generally good practice.

As of Java 8, the restriction that interfaces cannot contain static methods was eliminated, so there is typically little reason to provide a noninstantiable companion class for an interface. Many public static members that would have been at home in such a class should instead be put in the interface itself. Note,however, that it may still be necessary to put the bulk of the implementation code behind these static methods in a separate package-private class. This is because Java 8 requires all static members of an interface to be public.Java 9 allows private static methods,but static fields and static member classes are still required to be public.

<think>我们正在讨论的是在SofaMock测试框架中,当没有MockService时如何模拟静态方法。根据之前的讨论,我们知道在一般测试中可以使用PowerMockito等工具,但SofaMock可能有自己的机制。 然而,请注意:SofaMock(来自SOFAStack)是一个针对SOFA框架的测试工具,它主要支持对RPC服务、Spring Bean等进行模拟。根据官方文档,SofaMock本身并不直接支持静态方法的模拟。因此,在没有MockService的情况下,我们需要结合其他技术来实现对静态方法的模拟。 根据之前讨论的通用方案,我们可以考虑以下方法: 1. **使用PowerMockito**:尽管SofaMock不直接支持静态方法模拟,但我们可以结合PowerMockito来增强测试能力。PowerMockito可以模拟静态方法构造函数等。 2. **重构代码**:将静态方法调用包装到一个非静态的类中,然后使用SofaMock来模拟这个包装类。 由于问题明确提到“没有MockService”,我们假设不能使用SofaMock的MockService功能(例如@SofaReferenceMock等),那么我们就需要采用其他方式。 具体方案如下: ### 方案1:使用PowerMockito(推荐) 即使在使用SofaMock的测试中,我们也可以同时使用PowerMockito来模拟静态方法。只需要确保测试类同时使用PowerMockRunner和SofaMock相关的配置。 但是,注意:一个测试类只能使用一个Runner。因此,我们可以使用`PowerMockRule`(JUnit规则)来代替`PowerMockRunner`,这样就不需要改变原有的Runner(比如SpringRunner等)。 步骤: 1. 添加PowerMockRule依赖。 2. 在测试类中声明PowerMockRule。 3. 使用PowerMockito模拟静态方法。 示例: ```java import org.junit.Rule; import org.junit.Test; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.rule.PowerMockRule; @PrepareForTest({StaticClass.class}) // 指定要模拟的静态类 public class SofaMockWithPowerMockTest { // 使用PowerMockRule @Rule public PowerMockRule powerMockRule = new PowerMockRule(); @Test public void testStaticMethod() { // 模拟静态方法 PowerMockito.mockStatic(StaticClass.class); // 定义行为 PowerMockito.when(StaticClass.staticMethod()).thenReturn("mocked value"); // 调用被测方法,该方法内部调用了StaticClass.staticMethod() // ... 执行测试并验证 } } ``` 注意:如果测试类已经使用了其他规则(比如Spring的规则),则需要确保规则顺序。通常,PowerMockRule应该放在第一位。 ### 方案2:重构代码(依赖注入) 如果不想在测试中引入PowerMock,可以考虑重构代码,将静态方法调用封装到一个实例方法中,然后通过依赖注入(如Spring)来提供该实例。在测试中,我们可以使用SofaMock来模拟这个实例。 步骤: 1. 创建一个接口,定义静态方法的功能。 2. 创建一个实现类,调用静态方法。 3. 在业务代码中注入该接口,而不是直接调用静态方法。 4. 在测试中,使用SofaMock来模拟该接口。 示例: ```java // 步骤1:创建接口 public interface StaticMethodWrapper { String wrappedStaticMethod(); } // 步骤2:实现类 public class DefaultStaticMethodWrapper implements StaticMethodWrapper { public String wrappedStaticMethod() { return StaticClass.staticMethod(); // 调用静态方法 } } // 步骤3:在业务类中使用 public class BusinessService { @SofaReference private StaticMethodWrapper wrapper; // 通过SOFA注入 public String businessMethod() { // 使用wrapper调用 return wrapper.wrappedStaticMethod(); } } // 步骤4:测试 public class BusinessServiceTest { @Rule public SofaReferenceRule sofaReferenceRule = new SofaReferenceRule(); @Test public void testBusinessMethod() { // 创建模拟对象 StaticMethodWrapper mockWrapper = Mockito.mock(StaticMethodWrapper.class); when(mockWrapper.wrappedStaticMethod()).thenReturn("mocked value"); // 使用SofaMock注入模拟对象 sofaReferenceRule.field(BusinessService.class, "wrapper").mock(mockWrapper); BusinessService service = new BusinessService(); // 这里需要确保service中的wrapper被注入,可能需要通过反射设置,或者使用Spring测试框架 // 如果使用Spring,则可以使用@ContextConfiguration等注解 // 执行测试 String result = service.businessMethod(); assertEquals("mocked value", result); } } ``` 注意:这种方法需要代码重构,但更符合面向对象的设计原则,也更容易测试。 ### 方案3:使用反射(不推荐,仅作备用) 如果静态方法修改的是某个静态字段,我们可以通过反射来修改该字段的值,从而达到模拟的目的。但是这种方法有局限性,且不安全。 示例: ```java @Test public void testWithReflection() throws Exception { Field field = StaticClass.class.getDeclaredField("staticField"); field.setAccessible(true); Object originalValue = field.get(null); // 保存原值 try { field.set(null, "mocked value"); // 设置新值 // 执行测试 // ... } finally { field.set(null, originalValue); // 恢复原值 } } ``` 注意:这种方法只能修改静态字段,不能模拟静态方法的行为(除非静态方法的行为完全依赖于该字段)。 ### 总结 在没有MockService的情况下,我们依然可以通过其他方式模拟静态方法: 1. **推荐使用PowerMockRule**:结合PowerMockito来模拟静态方法,适用于测试代码。 2. **重构代码**:通过依赖注入和接口封装,使得静态方法变得可模拟,这是更可持续的方案。 3. **反射修改字段**:作为临时方案,但风险较高。 请根据项目实际情况选择合适的方法。 §§相关问题§§ 1. SofaMock如何结合PowerMockRule进行多规则测试? 2. 如何确保PowerMockRule与其他JUnit规则(如Spring规则)的顺序? 3. 在SOFA框架中如何实现依赖注入以替代静态方法调用?</think>在 SOFAMock 测试框架中,若需在没有 `MockService` 的情况下模拟静态方法,可通过以下方案实现: ### 一、核心解决方案(推荐) #### 1. **PowerMock + SOFAMock 集成方案** ```java @RunWith(PowerMockRunner.class) // 使用PowerMock运行器 @PowerMockRunnerDelegate(SofaTestRunner.class) // 委托给SOFAMock运行器 @PrepareForTest({StaticUtil.class}) // 声明需模拟的静态类 public class StaticMethodTest { @Test public void testStaticMethod() { // 1. 初始化静态类模拟 PowerMockito.mockStatic(StaticUtil.class); // 2. 定义静态方法行为 PowerMockito.when(StaticUtil.getConfig("key")) .thenReturn("mock_value"); // 3. 调用被测服务 String result = targetService.execute(); assertEquals("expected_result", result); } } ``` **关键点**: - 双运行器协同:`PowerMockRunner` 处理静态方法模拟,`SofaTestRunner` 管理 SOFA 容器 - 需添加依赖: ```xml <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> </dependency> ``` #### 2. **接口隔离重构法(可持续方案)** ```java // 1. 创建可替换接口 public interface ConfigLoader { String loadConfig(String key); // 替代静态方法 } // 2. 实现类包装静态方法 @Component public class StaticConfigLoader implements ConfigLoader { public String loadConfig(String key) { return StaticUtil.getConfig(key); // 原始调用 } } // 3. 业务类使用接口注入 public class BusinessService { @Autowired private ConfigLoader configLoader; // 依赖接口 public String execute() { return configLoader.loadConfig("key"); // 通过接口调用 } } // 4. 测试中模拟接口 @Test public void testWithMock() { ConfigLoader mockLoader = Mockito.mock(ConfigLoader.class); when(mockLoader.loadConfig("key")).thenReturn("mock_value"); targetService.setConfigLoader(mockLoader); // 依赖注入 assertEquals("expected_result", targetService.execute()); } ``` **优势**: - 完全兼容 SOFAMock 的依赖注入机制 - 消除对特殊测试工具的依赖 - 符合开闭原则[^1] ### 二、辅助方案 #### 1. **反射修改静态字段** ```java @Test public void testReflection() throws Exception { Field field = StaticUtil.class.getDeclaredField("CONFIG_CACHE"); field.setAccessible(true); Map<String, String> originalCache = (Map) field.get(null); // 保存原始状态 try { // 注入模拟数据 Map<String, String> mockCache = new HashMap<>(); mockCache.put("key", "mock_value"); field.set(null, mockCache); // 执行测试 assertEquals("expected_result", targetService.execute()); } finally { field.set(null, originalCache); // 恢复原始状态 } } ``` **适用场景**:静态方法依赖内部静态字段 #### 2. **类加载器隔离** ```java public class IsolatedClassLoader extends URLClassLoader { public IsolatedClassLoader(URL[] urls) { super(urls, null); // 父类加载器设为null实现隔离 } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (name.startsWith("com.static.")) { return findClass(name); // 强制重新加载静态类 } return super.loadClass(name); } } // 测试中使用 @Test public void testIsolated() throws Exception { URLClassLoader loader = new IsolatedClassLoader(new URL[]{...}); Class<?> staticCls = loader.loadClass("com.static.StaticUtil"); // 通过反射修改类行为 Method method = staticCls.getMethod("getConfig", String.class); // ... 定义模拟行为 } ``` ### 三、方案对比表 | 方案 | 适用场景 | SOFA兼容性 | 维护成本 | |---------------------|-------------------------|------------|----------| | PowerMock集成 | 遗留代码快速改造 | ★★★★ | 中 | | 接口隔离重构 | 新功能/可持续架构 | ★★★★★ | 低 | | 反射修改字段 | 简单静态字段注入 | ★★★★ | 高 | | 类加载器隔离 | 多版本/多模块静态类冲突 | ★★ | 极高 | ### 四、最佳实践 1. **新项目开发**:优先采用接口隔离方案,通过依赖注入避免静态方法调用 2. **遗留系统改造**: - 简单场景:用 PowerMock 快速实现 - 复杂场景:逐步重构为接口模式 3. **关键注意事项**: ```java @After public void tearDown() { PowerMockito.reset(StaticUtil.class); // 清理静态模拟状态 Mockito.reset(mockDependencies); // 重置模拟对象 } ``` - 需在测试类添加注解:`@PowerMockIgnore("javax.management.*")` 避免类加载冲突 > **架构建议**:静态方法本质是面向过程编程的产物,在 SOFA 这类面向组件的框架中,通过`@SofaService`/`@SofaReference`实现解耦,能从根本上提升可测试性[^1]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值