Class

本文探讨了面向对象设计的核心概念,包括类、接口、继承、多态等关键元素,并介绍了诸如特化、委派等设计模式,帮助读者更好地理解和运用面向对象编程。

Class

           类是对一组相似的东西的一般归纳,而对象则是这些东西本身。这里介绍的是与类相关的一些模式。

类(class)

           一个类是一个这样的声明:这些逻辑应该放在一起,它们的变化不像它们所操作的数据那么频繁;这些数据也应该放在一起,它们变化的频率差不多,并且由与之关联的逻辑来负责处理。

           当然也并不全都是数据变化,逻辑不变;有时候数据的变化会影响到逻辑的变化,就像是国产产品和进口产品对应的税金计算方式不同;有时候数据在计算过程中不会发生变化。需要我们学习如何用类来包装逻辑和如何表达逻辑的变化。

           类的继承体系也可以缩减代码量,但是它让代码变得更难懂,必须理解超类上下文。子类是超类的继承,代表我和超类很像,但是有些许的不同。这个在命名上要多加注意。

           在OOP中,类是相对昂贵的设计元素,一个类应该做一些直接和明显的事,不要泛滥。

简单的超类名(Simple Superclass Name)

           位于继承根上的类应该有简单的名字,用以描绘它的隐喻。一个好的命名有助于对整个程序继承体系的了解。Kent建议重要的类,尽量用一个单词来为它命名。

限定性的子类名(Qualified Subclass Name)

           子类的名字应该表达出它与超类之间的相似性和差异性。所以子类的名字有两重责任:1 描述这些类像什么 2 说明它们之间的区别是什么。

           我们在使用中声明一般使用的是超类,定义时才会用到子类。所以子类名称在交谈中使用的不频繁,这里不需要太过考虑子类名称的简单。我们可以在超类名称的基础上增加1、2个单词来得到我们的子类名称。

           当然实际操作中不会有这么简单的处理方式,并不是所有的子类名都适合在超类名上扩展。就好比:超类名为Language,那么子类完全可以叫做Chinese,而不是Chinese Language。尤其是对于多重继承来说,在超类名的基础上扩展绝对是一场灾难。

           要根据实际情况,灵活处理,谨记子类名的命名原则:表达相似性和差异性。

抽象接口(Abstract Interface)

           将接口与实现分离。意味着决策不应该暴露给不必要的地方。抽象借口在Java中意味着interface和超类,其中超类是抽象的。

           一般认为接口就意味着灵活。但这里谈论灵活的时候我们都忽略了2个东西。

1、  灵活是需要成本的。出于成本的考虑,我们应该在真正需要这种灵活性的时候才引入接口

2、  软件存在不确定性。我们无法预料软件变化的方向。由于面向对象的特性,新增对象的成本远低于新增方法的成本,所以不必要的灵活处理,可能在后期给我们造成极大的修改难度。

所以,我们应该在确定无疑地需要灵活时,才应该引入这种灵活性。谨记:灵活是有成本的。

Interface

           Java中Interface表达“这是我要完成的任务,除此之外的细节不归我关心”。

           对interface增加或是修改方法,就必须改变它的所有实现类,这是interface的一个使用上的风险。另外interface要求所有的方法必须为public的,这比较容易暴露我们的设计思想,也是一个风险。

           对interface的命名也存在2中方式,取决于我们如何看待它们。一种是把它当作“没有实现的类”;另一种就是从命名中声明为“抽象的类”。

有版本的Interface(Versioned Interface)

           如果我们一定要修改一个接口,但又无法修改它的所有实现类怎么办?这里我们可以考虑使用有版本的interface。声明一个新的interface,使它继承原来的interface,然后再其中增加操作。如果使用者需要新增的功能,就用这个interface,否则的话就继续无视新的interface。但使用新的interface时有可能需要instanceof来判别。

抽象类(Abstract Class)

抽象接口的另一个实现方式是使用超类。超类是抽象的,因为超类的引用可以在运行时替换为任何子类的对象;至于这个超类在Java的语法意义上是不是抽象的,这并不重要。

那么我们什么时候使用超类,什么时候使用interface呢?这个取决于①接口会如何发生变化,实现类是否需要同时支持多个接口;②抽象借口需要支持实现的变化以及接口本身的变化两种类型的变化。

Interface对第二点的支持不佳,一旦接口变化就需要实现类进行修改,多了以后就容易致使程序瘫痪,或是借助有版本的interface

抽象类没有这些限制。只要提供了默认实现就不会影响到实现类。但是一个实现类只能继承一个抽象类(实现类对抽象类必须忠贞不二,^_^),限制了它的使用。

值对象(Value Object)

           这种对象的行为就好像数值一样。就好像是Integer似地,虽然是类,但我们一般把它当作是int来使用的。

           值对象的所有状态都应该在构造子中传入,其他地方不再提供改变其内部状态的方式。值对象的操作总是返回新的对象,操作的发起者要自己保存返回的对象。

           值对象这种风格适用于其中状态不会发生变化的情况。

特化(Specialization)

           清晰的描述计算过程中相似性与差异性的相互作用,可以让程序更容易阅读、使用和修改。

           实际代码中经常出现这样的情况:数据可能大多相同,但略有些区别;逻辑可能大多相同,但略有些区别。

           特化我觉得在这里可以用《设计模式》中提到的面向对象的一个特性来解释,就是类、方法的职能要单一。如果职能不单一,那么我们就很难区分功能,也就无从判别其中的相似性和差异性了。

子类(SubClass)

           声明一个子类就是说,我与超类很相像,就是在XXX上有点不同。是面向对象很有用的一个手段。

           当然,继承也不是万灵丹,也是有着诸多的限制。首先,写子类前必须先了解超类的结构和功能;其次,后期如果要修改超类,可能会对它的子类造成灾难性的破坏;再次,子类继承于超类,可以利用超类功能,但是同时也是对子类的一种约束,限制了子类的功能范围。

           另外超类的方法功能是否单一,也会影响到子类的结构。如果超类的每一个方法都具有单一性,那么实现子类时,只需要覆写特定的几个或一个方法,也更加容易表达出子类的意图。否则的话,覆写超类方法时,还需要拷贝一堆无用代码过来,可真就无趣的很,也背离了我们特化(Specialization)的要求。

实现器(Implementor)

           覆盖一个方法,从而表现一种计算上的变化。 Kent在文中使用了“意图”和“实现”两个词。“意图”暴露给客户,告诉他们我们要做什么;“实现”隐藏起来,是由开发者完成的具体实现。

           为了在一个类内达到“意图”和“实现”分离,Kent提出了另一个面向对象的特性——多态。

           多态,保证了同一个“意图”可以有多个不同的“实现”。这里用“特化”声明了相似性。

内部类(Inner Class)

           我们可以内部类相当于将局部有用的代码放在一个私有的类中。当我们需要把某一部分计算逻辑包装起来,但是又不想新建一个文件来安置全新的类,这时就可以声明一个小的私有类(内部类),这样就可以低成本的获得类的大部分好处。

           内部类有一个特点:当内部类被实例化时,它的对象会悄悄地获得创建它的那个对象。如果,我们想将内部类和所处的对象分离,我们可以将其声明为static的。

实例特有的行为(Instance-specific Behavior)

           理论上来讲,每个类的所有实例逻辑都应该是相同的。但在实际中每个实例的逻辑都有不同。这会导致代码在阅读时不易理解,最好在实例创建的时候就确定所有的行为,并不再变化。

条件语句(Conditional)

           要实现实例特有的行为,if/then和switch语句是最简单的方式。但是条件语句需要重复的话,造成的影响就会很大,不如把条件逻辑编程消息,发送给子类或委派。这里可以参照Martin Fowler《重构》中关于影片租赁系统的代码的重构,很精彩。

委派(Delegation)

           把部分工作委派给不同类型的对象,不变的逻辑放在发起委派的类中,变化的逻辑交给被委派的对象。

           这里我们可以考虑把委派的对象作为参数传递给接受委派的方法。这里可以参见设计模式中的策略模型。

可插拔的选择器(Pluggable Selector)

           通过反射来调用方法,以表现不同的逻辑。

           使用可插拔选择器的代价很大,比如你可能删除一段看起来从未被调用的代码而导致系统崩溃,因为这段代码是别人通过发射调用的。所以需要解决某些特别困难的问题时才值得用这样的代价来换取,因此应该严格限制对这种技巧的使用。

匿名内部类(Anonymous Inner Class)

           这种方法可以创建“只在一处使用的类”,这些类可以覆盖一个或者多个方法,完全为了当前的用途。由于只在一处使用,这样的类可以被隐式的引用,不需要有名字。

           匿名内部类有个很麻烦的地方,就是在TDD中很难直接测试到。

类库(Library Class)

           如果一组功能不适合放进任何对象,就将其描述为一组静态的方法。就像是Collections,Arrays等就是类库。

下载前必看:https://pan.quark.cn/s/a4b39357ea24 在本资料中,将阐述如何运用JavaScript达成单击下拉列表框选定选项后即时转向对应页面的功能。 此种技术适用于网页布局中用户需迅速选取并转向不同页面的情形,诸如网站导航栏或内容目录等场景。 达成此功能,能够显著改善用户交互体验,精简用户的操作流程。 我们须熟悉HTML里的`<select>`组件,该组件用于构建一个选择列表。 用户可从中选定一项,并可引发一个事件来响应用户的这一选择动作。 在本次实例中,我们借助`onchange`事件监听器来实现当用户在下拉列表框中选定某个选项时,页面能自动转向该选项关联的链接地址。 JavaScript里的`window.location`属性旨在获取或设定浏览器当前载入页面的网址,通过变更该属性的值,能够实现页面的转向。 在本次实例的实现方案里,运用了`eval()`函数来动态执行字符串表达式,这在现代的JavaScript开发实践中通常不被推荐使用,因为它可能诱发安全问题及难以排错的错误。 然而,为了本例的简化展示,我们暂时搁置这一问题,因为在更复杂的实际应用中,可选用其他方法,例如ES6中的模板字符串或其他函数来安全地构建和执行字符串。 具体到本例的代码实现,`MM_jumpMenu`函数负责处理转向逻辑。 它接收三个参数:`targ`、`selObj`和`restore`。 其中`targ`代表要转向的页面,`selObj`是触发事件的下拉列表框对象,`restore`是标志位,用以指示是否需在转向后将下拉列表框的选项恢复至默认的提示项。 函数的实现通过获取`selObj`中当前选定的`selectedIndex`对应的`value`属性值,并将其赋予`...
### Java 中 loadClass 的使用 `loadClass` 是 `ClassLoader` 类的一个实例方法,用于根据类的全限定名查找并加载类。其基本使用方式如下: ```java // 获取类加载器实例 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); try { // 加载类 Class<?> clazz = classLoader.loadClass("com.example.MyClass"); // 使用加载后的类 // ... } catch (ClassNotFoundException e) { e.printStackTrace(); } ``` `loadClass` 方法遵循双亲委派模式。当调用 `loadClass` 方法时,会先在父类加载器(包括 `bootstrap` 类加载器)中查找该类,如果找到则由对应的类加载器将该类加载到 JVM 中;如果没有找到,才会按照 `findClass` 方法继续查找 [^1]。 ### loadClass 与 defineClass 的对比 #### 功能用途 - `loadClass`:主要负责类的加载过程,它会根据类的全限定名查找并加载类。在加载过程中,会先尝试让父类加载器进行加载,若父类加载器无法加载,再由当前类加载器进行加载。该方法将 `Class` 文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化 [^1][^2]。 - `defineClass`:是将字节码数据转换为 `Class` 对象的方法。它接受一组字节数组,将其具体化为一个 `Class` 类型实例。通常在重写 `findClass` 方法时使用 `defineClass` 方法,把读取到的字节码数据转化为 `Class` 对象。若不想把 `class` 加载到 JVM 中,也可单独使用 `getConstructor` 和 `newInstance` 来实例化一个对象 [^1][^3]。 #### 使用场景 - `loadClass`:一般用于常规的类加载场景,例如在程序中动态加载某个类时使用。 - `defineClass`:常用于自定义类加载器中,当需要从特别来源(如网络、数据库等)获取字节码数据,并将其转换为 `Class` 对象时使用。 #### 调用关系 `loadClass` 方法在查找类过程中,若父类加载器无法加载,会调用 `findClass` 方法,而 `findClass` 方法中通常会使用 `defineClass` 方法将字节码数据转换为 `Class` 对象 [^1]。 ### 示例代码展示两者配合 ```java import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; // 自定义类加载器 class CustomClassLoader extends ClassLoader { private String classPath; public CustomClassLoader(String classPath) { this.classPath = classPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] classData = getClassData(name); if (classData != null) { return defineClass(name, classData, 0, classData.length); } } catch (IOException e) { e.printStackTrace(); } return super.findClass(name); } private byte[] getClassData(String className) throws IOException { String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; try (FileInputStream fis = new FileInputStream(path); ByteArrayOutputStream bos = new ByteArrayOutputStream()) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); } return bos.toByteArray(); } } } public class Main { public static void main(String[] args) { CustomClassLoader customClassLoader = new CustomClassLoader("path/to/classes"); try { // 使用 loadClass 加载类 Class<?> clazz = customClassLoader.loadClass("com.example.MyClass"); System.out.println("Class loaded: " + clazz.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值