陈灯可重用代码段管理器_利用可重用代码,第2部分

系列的前几期文章重点介绍了紧急设计中显而易见的第一步: 发现惯用模式。 找到一个后,您将如何处理? 这个问题的答案是本期的重点,这是多部分文章的第二部分。 第1部分 (讨论代码与设计之间的关系)涵盖了软件设计实际上是指解决方案的整个源代码这一观点的理论基础。 一旦将您的观点转变为将所有代码视为真实的设计,就可以开始考虑在语言级别上整合设计元素,而不仅仅是在图和其他设计辅助工件领域。 在这里,我将讨论一旦您在代码中发现了可重用的设计(包括收获这些模式的技术)后该怎么做。 我将以简单的API来收集它们,然后说明一种收集技术,使您能够将这些元素与其他代码区分开。

收集模式作为API

捕获惯用模式的最简单方法是将它们提取为自己的API或框架。 您使用的大多数开源框架都是与解决特定问题有关的惯用模式集。 例如,Web框架包含构建Web应用程序所需的所有API元素,这些API元素是从其他工作的Web应用程序中预先获取的。 例如,Spring是用于处理依赖项注入和构造的技术惯用模式的集合,而Hibernate封装了用于对象关系映射的模式(请参阅参考资料 )。

当然,您可以在代码中执行相同的操作。 到目前为止,这是最简单的技术,因为您只是在更改代码的结构(通常通过选择的IDE中的重构支持)。 在第1部分以及讨论设计模式的“ 语言,表现力和设计,第2部分 ”中,可以找到该技术的许多示例。

避免结构重复

API偶然会鼓励结构重复。 使用API​​可能会很吵,因为您经常必须使用主机对象来调用API。 考虑清单1中的示例(该示例调用与铁路车辆有关的API):

清单1.访问Car API
Car2 car = new CarImpl();
MarketingDescription desc = new MarketingDescriptionImpl();
desc.setType("Box");
desc.setSubType("Insulated");
desc.setAttribute("length", "50.5");
desc.setAttribute("ladder", "yes");
desc.setAttribute("lining type", "cork");
car.setDescription(desc);

强制用户键入宿主对象( desc )将不必要的噪音添加到代码中。 大多数API都包含主机对象作为API的入口点,并且您必须随身携带它们才能访问API。

存在一些缓解API中此问题的技术。 一种使用鲜为人知的Java语法,该语法允许您通过匿名内部类的作用域“承载”主机对象,如清单2所示:

清单2.使用匿名内部类携带宿主对象
MarketingDescription desc = new MarketingDescriptionImpl() {{
    setType("Box");
    setSubType("Insulated");
    setAttribute("length", "50.5");
    setAttribute("ladder", "yes");
    setAttribute("lining type", "cork");

}};

为了让您理解清单2,我必须深入探讨Java语言如何处理初始化。 考虑清单3中的代码:

清单3. Java语言的初始化程序
public class InitializerDemo {
    public InitializerDemo() {
        out.println("in constructor");
    }

    static {
        out.println("in static initializer");
    }

    {
        out.println("in instance initializer");
    }

    public static void main(String[] args) {
        out.println("in main() method");
        new InitializerDemo();
    }
}

清单3中的示例说明了Java语言中的四种不同的初始化技术:

  • main()方法中
  • 在构造函数中
  • 在静态初始化程序块中,该类在加载类时执行
  • 在初始化程序块中,该程序块在构造函数之前执行

此执行顺序如图1所示:

图1. Java语言的初始化顺序
Java初始化顺序的图示

静态初始化程序首先在加载类时运行,然后运行main方法(也是静态的)。 之后,Java平台收集所有实例初始化程序块,并在构造函数之前执行它们,最后是构造函数本身。 实例初始化器使您可以执行匿名内部类的构造代码。 事实上,它是唯一真正的初始化机制,因为它是不可能写出一个构造一个匿名内部类-构造必须具有相同的名称作为类,但一个匿名内部类的类没有名字。

通过使用本质上是愚蠢的Java技巧,您可以避免重用要执行的一系列方法的主机名。 但是,这是以一种奇怪的语法为代价的,该语法可能会让您的同事挠头。

不足之处

将惯用模式提取为API是一种完全有效的技术,并且可能是利用您发现的可重用宝石的最常用方法。 这种方法的缺点在于它的常规性:很难区分您提取的设计元素,因为它们看起来就像您的所有其他代码一样。 您的项目后继者将很难理解您创建的API与周围的代码略有不同,因此您在发现模式时的侦探工作可能会浪费掉。 但是,如果您可以使惯用模式在其他代码中脱颖而出,则很容易看出它实际上是与众不同的。

使用元编程

元编程提供了一种区分模式代码与实现代码的好方法,因为您可以通过使用与代码有关的代码来表达模式。 属性是Java语言提供的一种不错的技术。 您可以定义属性来创建声明性元编程标签。 属性提供了表达概念的简洁方法。 通过将其定义为属性并修饰类的相关部分,可以将许多功能打包到一个很小的空间中。

这是一个很好的例子。 在大多数项目中,一种常见的技术惯用模式是验证,它非常适合于声明性代码。 如果您将验证模式作为属性来获取,则可以使用清晰的验证约束来标记您的代码,这些约束不会干扰代码的主旨。 考虑清单4中的代码:

清单4. MaxLength属性
public class Country {
	private List<Region> regions = new ArrayList<Region>();
	private String name;
	
	public Country(String name){
		this.name = name;
	}
	
	@MaxLength(length = 10)
	public String getName(){
		return name;
	}
	
	public void addRegion(Region region){
		regions.add(region);
	}
	
	public List<Region> getRegions(){
		return regions;
	}
}

使用属性标记代码元素的能力声明了您的意图,即外部行为会对随后的代码进行操作。 这进而使得更容易区分图案部分和实施部分。 您的验证代码会突出显示,因为它看起来与周围的其他代码不同。 通过功能对代码进行这种划分,可以轻松地确定特定的职责,进行重构和维护。

MaxLength验证器指定Country名称不得超过10个字符。 属性声明本身出现在清单5中:

清单5. MaxLength属性声明
@Retention(RetentionPolicy.RUNTIME)
public @interface MaxLength {
	int length() default 0;
}

MaxLength验证器的实际功能驻留在两个类中:一个名为Validator的抽象类,以及一个名为MaxLengthValidator的具体实现。 清单6中显示了Validator类:

清单6.基于抽象属性的Validator
public abstract class Validator {

    public void validate(Object obj) throws ValidationException {
        Class clss = obj.getClass();
        for(Method method : clss.getMethods())
            if (method.isAnnotationPresent(getAnnotationType()))
                validateMethod(obj, method, method.getAnnotation(getAnnotationType()));
    }

    protected abstract Class getAnnotationType();
    protected abstract void validateMethod(
        Object obj, Method method, Annotation annotation);
}

此类通过查看getAnnotationType()来遍历类中的方法,以确定这些方法是否用特定的属性修饰。 找到一个时,将执行validateMethod()方法。 清单7中显示了MaxLengthValidator类的实现:

清单7. MaxLengthValidator
public class MaxLengthValidator extends Validator {

    protected void validateMethod(Object obj, Method method, Annotation annotation) {
        try {
            if (method.getName().startsWith("get")) {
                MaxLength length = (MaxLength)annotation;
                String value = (String)method.invoke(obj, new Object[0]);
                if ((value != null) && (length.length() < value.length())) {
                    String string = method.getName() + " is too long." + 
                        "Its length is " + value.length() + 
                        " but should be no longer than " + length.length();
                    throw new ValidationException(string);
                }
            }
        } catch (Exception e) {
            throw new ValidationException(e.getMessage());

        }
    }

    @Override
    protected Class getAnnotationType() {
        return MaxLength.class;
    }
}

此类检查要进行潜在验证的方法是否以get开头,然后从注释中get元数据,最后根据声明的长度检查属性的length字段的值,并在违反规则时引发验证错误。

属性可以完成一些非常复杂的工作。 考虑清单8中的示例:

清单8.具有唯一性验证的类
public class Region {
    private String name = "";
    private Country country = null;
    
    public Region(String name, Country country) {
        this.name = name;
        this.country = country;
        this.country.addRegion(this);
    }

    public void setName(String name){
        this.name = name;
    }
    
    @Unique(scope = Country.class)
    public String getName(){
        return this.name;
    }
    
    public Country getCountry(){
        return country;
    }
}

清单9中所示的Unique属性的声明非常简单:

清单9. Unique属性
@Retention(RetentionPolicy.RUNTIME)
public @interface Unique {
	Class scope() default Unique.class;
}

Unique属性实现类扩展了清单6中所示的抽象Validator类。 其来源如清单10所示:

清单10.唯一的验证器实现
public class UniqueValidator extends Validator{

  @Override
  protected void validateMethod(Object obj, Method method, Annotation annotation) {
    Unique unique = (Unique) annotation;
    try {
      Method scopeMethod = obj.getClass().getMethod("get" + 
          unique.scope().getSimpleName());
      Object scopeObj = scopeMethod.invoke(obj, new Object[0]);
      
      Method collectionMethod = scopeObj.getClass().getMethod(
          "get" + obj.getClass().getSimpleName() + "s");
      List collection = (List)collectionMethod.invoke(scopeObj, new Object[0]);
      Object returnValue = method.invoke(obj, new Object[0]);
      for(Object otherObj: collection){
        Object otherReturnValue = otherObj.getClass().
            getMethod(method.getName()).invoke(otherObj, new Object[0]);
        if (!otherObj.equals(obj) && otherReturnValue.equals(returnValue))
          throw new ValidationException(method.getName() + " on " + 
            obj.getClass().getSimpleName() + " should be unique but is not since");
      }
    } catch (Exception e) {
      System.out.println(e.getMessage());
      throw new ValidationException(e.getMessage());
    }
  }

  @Override
  protected Class getAnnotationType() {
    return Unique.class;
  }
}

此类必须做大量工作以确保国家/地区名称的值唯一,但这是Java编程中的强大属性的一个很好的例子。

属性是Java语言的一个受欢迎的补充。 它们使您可以简洁地定义在目标类中几乎没有语法宿醉影响的行为。 但是,与您可以在JVM等更具表达能力的语言(例如JRuby)中进行的工作相比,它们仍然受到限制。

使用JRuby的粘性属性

Ruby语言还具有属性(尽管它们没有“属性”之类的特殊名称,它们是Ruby提供的许多元编程技术之一)。 这是一个例子。 考虑清单11中的测试类:

清单11.测试复杂的计算
class TestCalculator < Test::Unit::TestCase
  def test_complex_calculation
    assert_equal(4, Calculator.new.complex_calculation)
  end
end

假设complex_calculation方法花费的时间如此之长,以至于您只想在进行验收测试时运行它,而不是在单元测试运行时运行它。 清单12中显示了一种限制它的方法:

清单12.限制测试范围
class TestCalculator < Test::Unit::TestCase

  if ENV['BUILD'] == 'ACCEPTANCE'
    def test_complex_calculation
       assert_equal(4, Calculator.new.complex_calculation)    
    end
  end
  
end

这是一种与测试有关的技术惯用模式,在很多情况下我都可以轻松预期到这种测试。 将方法声明包装在if块中会给我的代码增加一些丑陋的复杂性,因为现在并不是所有的方法声明都以相同的缩进级别出现。 相反,我将使用属性来捕获该模式,如清单13所示:

清单13.在Ruby中声明一个属性
class TestCalculator < Test::Unit::TestCase  
  extend TestDirectives 
  
  acceptance_only
  def test_complex_calculation
    assert_equal(4, Calculator.new.complex_calculation)        
  end
end

这个版本更加简洁易读。 清单14中所示的实现很简单:

清单14.属性声明
module TestDirectives
  def acceptance_only
    @acceptance_build = ENV['BUILD'] == 'ACCEPTANCE'
  end
  
  def method_added(method_name)
    remove_method(method_name) unless @acceptance_build
    @acceptance_build = false
  end
end

在Ruby中用很少的代码可以实现多少成就,这是非常了不起的。 清单14声明了一个module ,它是Ruby的混入版本。 混入包含可以include在类中的功能,以便将该功能添加到类中。 您可以将其视为一种界面,但是可以包含代码。 该模块定义了一个名为acceptance_only的方法,该方法检查BUILD环境变量以查看正在执行哪个测试阶段。 设置此标志后,模块将利用挂钩方法。 Ruby中的挂钩方法在解释时(而不是运行时)执行,并且每次向类添加新方法时都会触发此特定的挂钩方法。 执行此方法时,如果设置了acceptance_build标志,它将删除刚刚定义的方法。 然后,将标志设置回false 。 (否则,此属性将影响所有后续方法声明,因为该标志将保持为真。)如果您希望它影响包含多个方法的代码块,则可以删除该标志的重置,从而使此行为一直保持到其他状态。 (例如用户定义的unit_test属性)对其进行更改。 (这些非正式地称为粘性属性 。)

为了说明这种机制的强大功能,Ruby语言本身使用粘性属性来声明privateprotectedpublic class-scope修饰符。 没错-Ruby中的类作用域指定不是关键字,它们只是粘性属性。

结论

在本期中,我演示了如何使用API​​和属性作为收集惯用模式的技术。 如果您找到一种使采摘的模式从其他代码中脱颖而出的方法,那么阅读这两种代码会更容易,因为它们不会互相污染。

在下一部分中,我将继续展示如何收集惯用模式,以及通常用于构建特定于域的语言的一系列技术。


翻译自: https://www.ibm.com/developerworks/java/library/j-eaed12/index.html

陈灯重用代码管理器为一款个人软件作品,其可作为一款个人和团队内部的代码管理软件使用,实现了可重用代码的入库、搜索和共享等功能。软件具有代码搜索准确方便、代码颜色标识、搜索词导航以及类似Google的智能搜索框等特点。 软件包括桌面版本和插件版本两种版本,桌面版本为独立的应用程序,不与IDE集成,使用范围更加广泛,目前最新版本为2.3;插件版本集成在Visual Studio(5.0~10.0)集成开发环境中,使用更为方便。该发布版本为插件版4.0版本。 该版本完成了以下工作: 1、实现了类似Google的智能搜索框,能够根据关键词提示并自动完成搜索词的输入,使得搜索更加方便、准确。 2、增加了对重复入库代码的过滤功能。 3、代码搜索界面中增加了“最近入库”和“最近使用”选项卡,使用户能够方便的查看最近入库和最近使用的代码。 4、软件界面中添加了工具提示,简化用户的使用。 5、搜索范围扩展到了代码分类、开发语言和开发环境。 6、代码库导入完成后显示导入代码条数信息。 7、提供了多种搜索算法供用户选择。 8、增加了开发环境管理和开发语言管理功能。 9、解决了代码搜索不准确的问题。 10、解决了使用过程中,代码信息中的单引号会增加的问题。 11、解决了前版本中存在的其它若干bug。
陈灯重用代码管理器为一款个人软件作品,其可作为一款个人和团队内部的代码管理软件使用,实现了可重用代码的入库、搜索和共享等功能。软件包括桌面版本和插件版本,桌面版本为独立的应用程序,不与IDE集成,使用范围更加广泛,目前最新版本为2.5;插件版本集成在Visual Studio(5.0~10.0)集成开发环境中,使用更为方便。该发布版本为插件版5.0版本。 该版本完成了以下工作: 1、优化代码搜索引擎,搜索更加高效、准确,1万条代码平均搜索时间在1S钟以内 2、采用数据预加载和异步数据加载技术优化了界面启动的速度 3、增加了附件上传、下载功能 4、界面大小可动态缩放并自动记录用户设置的大小 5、增加了编程语言与颜色方案关联功能并提供了多种颜色方案供用户选择 6、可直接在代码搜索界面中进行代码编辑操作 7、编辑代码时隐藏代码搜索界面 8、代码编辑器支持查找和替换等高级功能 9、增加了设置代码编辑器字体功能 10、增加了关键词管理、清空代码库和压缩代码库等功能 11、优化了代码导入、导出功能,改善用户体验 12、增加了隐藏面板功能,可动态扩展代码编辑器大小,方便录入和查看 13、美化了界面 14、解决了前版本中存在的其它若干bug 该版本中包含了作者陈灯积累的代码400多条,用户若不愿使用可以通过代码搜索界面中的清空代码库功能进行清空。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值