- 浏览: 58456 次
- 性别:
- 来自: 成都
-
文章分类
最新评论
-
chiqinghaichi:
楼主好!菜鸟一枚,试了下你的程序,当访问 http://loc ...
SpringMVC -
随意而生:
复习复习 不错
SpringMVC -
ccii:
cylboke 写道他的意思是“orcle”写错了吧?我猜的! ...
Oracle基本操作 -
happy200318:
给个赞,不错
SpringMVC -
cylboke:
他的意思是“orcle”写错了吧?我猜的!!!
Oracle基本操作
本章内容:
1. 使类和成员的可访问性最小化
2. 在公有类中使用访问方法而非公有域
3. 使可变性最小化
4. 复合优先于继承
5. 要么为继承而设计,并提供文档说明,要么就禁止继承
6. 接口优于抽象类
7. 接口只用于定义类型
8. 类层次优于标签类
9. 用函数对象表示策略
10. 优先考虑静态成员类(嵌套类)
1. 使类和成员的可访问性最小化
设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离开来。然后,模块之间只通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况。这个概念被称为信息隐藏或封装,是软件设计的基本原则之一。信息隐藏可以有效地解除组成系统的各模块之间的耦合关系,使得这些模块可以独立的开发、测试、优化、使用、理解和修改。
Java程序设计语言提供了许多机制来协助信息隐藏。访问控制机制决定了类、接口和成员的可访问性,实体的可访问性是由该类实体声明所在的位置以及该实体声明中所出现的访问修饰符(private、protected、public)共同决定的。规则是尽可能地使每个类或者成员不被外界访问,换句话说应该使用与你正在编写的软件功能一致、尽可能最小的访问级别。
访问级别:
私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员。
包级私有的:声明该成员的包内部的任何类都可以访问这个成员,缺省的访问级别。
受保护的:声明该成员的类的子类和该成员的包内部的类可以访问这个成员。
公有的:在任何地方都可以访问该成员。
(1)如果类或者接口能够做成包级私有的,它就应该被做成包级私有。这样在以后如果需要进行修改、替换、或者删除无需担心会影响到现有的客户端程序。如果你把它做成公有的,你就有责任永远支持它,以保持它们的兼容性。
(2)如果一个类只是在某一个类的内部被用到,就应该考虑使它成为那个类的私有嵌套类。
(3)如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。如果一个类实现了一个接口,那么接口中所有的类方法在这个类中也都必须被声明为公有的,因为接口中的所有方法都隐含着公有访问级别。
(4)不能为了测试,而将类、接口或者成员变成包的导出的API的一部分。可以让测试作为被测试的包的一部分来支行,从而能够访问它的包级私有的元素。
(5)实例域决不能是公有的。如果域是final的,或者是一个指向可变对象的final引用,那么一旦使这个域成为公有的,就放弃了对存储在这个域中的值进行限制的能力。这意味着,你也放弃了强制这个域不可变的能力。同时,当这个域被修改的时候,你也失去了对它采取任何行动的能力。因此包含公有可变域的类并不是线程安全的。
(6)长度非零的数组总是可变的,所以类具有公有的静态final数组域和返回这种域的方法都是错误的,如果类具有这样的域或者访问方法,客户端将能够修改数组中的内容。
public static final Thing[] VALUES={....};
许多IDE会产生返回指向私有数组域的引用的访问方法,这样就会产生上面的问题。修正这个问题有两种方法:
一是使公有数组变成私有的,并增加一个公有的不可变列表。
二是使数组变成私有的,并添加一个公有方法,它返回私有数组的一个备份。
总而言之,你应该始终尽可能地降低可访问性。在设计了一个最小的公有API之后,应该防止把任何散乱的类、接口和成员变成API的一部分。除了公有静态final域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都是不可变的。
2. 在公有类中使用访问方法而非公有域
如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改变该类的内部表示法的灵活性。
class Point{
private int a;
private int b;
public Point(int a, int b){
this.x = y;
this.y = y;
}
public int getA(){return a;}
public int getB(){return b;}
public void setA(int a){this.a = a;}
public void setB(int b){this.b = b;}
}
如果类是包级私有的,或者是私有的嵌套类,直接暴露它的数据域并没有本质的错误。如有必要,不改变包之外的任何代码而只改变内部数据表示法也是可以的。
让公有类直接暴露域虽然从来都不是种好办法,但是如果域是不可变的,这种做法的危害就比较小一些。
3. 使可变性最小化
不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。不可变类比可变类更加易于设计、实现和使用。它们不容易出错,且更加安全。如String、基本类型的包装类、BigInteger、BigDecimal等。
为了使类成为不可变,要遵循下面五条规则:
(1)不要提供任何会修改对象状态的方法(mutator)
(2)保证类不会被扩展,防止子类假装对象的状态已经改变,从而破坏该类的不可变行为。一般防止子类化就是使这个类成为final的。
(3)使所有的域都是final的,或者让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂来代替公有的构造器
(4)使所有的域都成为私有的,防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象
(5)确保对于任何可变组件的互斥访问
不可变对象本质上是线程安全的,它们不要求同步。不可变类应该充分利用这种优势,鼓励客户端尽可能地重用现有的实例,要做到这一点,一个很简便的办法就是对于频繁用到的值,为它们提供公有的静态final常量,
public static final String ERROR_TYPE = "xxx";
这种方法可以被进一步扩展,不可变的类可以提供一些静态工厂,把频繁被请求的实例缓存起来,从而当现有实例可以符合请求的时候,就不必创建新的实例。
不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。
如果能够精确地预测出客户端将要在不可变的类上执行哪些复杂的多阶段操作, 可以提供一种包级私有的可变配套类,如String类的可变配套类StringBuilder。
坚决不要为每个get方法编写一个相应的set方法,除非有很多的理由要让类成为可变的类,否则就应该是不可变的。
4. 复合优先于继承
继承是实现代码重用的有力手段,但它并非永远是完成这项工作的最佳工具。使用不当会导致软件变得脆弱。在包的内部使用继承是非常安全的,在这里子类和超类的实现都处在同一个程序员的控制之下。对于专门为了继承而设计、并且具有很好的文档说明的类来说,使用继承也是非常安全的。
继承打破了封装性,换句话说,子类依赖于其超类中特定功能的实现细节,如果超类的实现随着版本的不同而有所变化,子类可能会遭到破坏,即使子类的代码完全没有改变。因而子类必须要跟着其超类的更新而演变。
幸运的是,有一种办法可以避免前面提到的所有问题。不用扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例。这种设计被称做“复合”,因为现有的类变成了新类的一个组件。新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果,这被称为转发,新类中的方法被称为转发方法。这样得到的类将会非常稳固,它不依赖于现有类的实现细节,即使现有的类添加了新的方法,也不会影响新的类。新类被称为包装类,这也正是Decorator模式,注意包装类不适合用在回调框架中。实践中包装类不会对性能造成很大的影响。
只有当子类真正是超类的子类型时,才适合用继承。即是说只有当两者之间确实存在“is-a”关系的时候,一个类才能扩展另一个类。
5. 要么为继承而设计,并提供文档说明,要么就禁止继承
首先,该类的文档必须精确地描述覆盖每个方法带来的影响。换句话说,该类必须有文档说明它可覆盖的方法的自用性。即对于每个公有的或受保护的方法或者构造器,它的文档必须指明该方法或者构造器调用了哪些可覆盖的方法,是以什么顺序调用的,每个调用的结果又是如何影响后续的处理过程的(所谓可覆盖的方法是指非final的,公有的或受保护的)。如果方法调用到了可覆盖的方法,在它的文档注释的末尾应该包含关于这些调用的描述信息。
为了能编写出更加有效的子类,类必须通过某种形式提供适当的钩子(hook),以便能够进入到它的内部工作流程中,这种形式可以是精心选择的受保护的方法,也可以是受保护的域。
构造器决不能调用可被覆盖的方法,无论是直接调用还是间接调用。
如果类是为了继承而被设计的,实现Cloneable或Serializable接口都不是个好注意,因为它们把一些实质性的负担转嫁到了扩展这个类的程序员的身上。当然可以采取一些特殊的手段使得子类实现这些接口(在谨慎的覆盖clone和谨慎的实现Serialable接口两节中有说到),即无论是clone还是readObject方法都不可以调用可覆盖的方法,不管是直接还是间接的方式。如果你决定实现Serializable接口,并且该类有一个readResolve或者writeReplace方法,就必须使readResolve或者writeReplace成为受保护的方法,而不是私有的方法,否则子类将会不声不响的忽略掉这两个方法。
对于那些并非为了安全地进行子类化而设计和编写文档的类,要禁止子类化。禁止子类化一种方法就是把类声明为final的,另一种方法就是把所有的构造器都变成私有的,或者包级私有的,并增加一些公有的静态工厂来替代构造器。
6. 接口优于抽象类
这两者机制之间最明显的区别在于,抽象类允许包含某些方法的实现,但是接口则不允许,接口则不允许。另一个重要的区别在于定义的类必须成为抽象类的一个子类,而java只允许单继承,故抽象类作为类型定义受到了极大的限制。
接口是定义mixin(增加功能混合到类型的主要功能上,如Comparable)的理想选择。mixin是指这样的类型:它除了实现它的“基本类型”之外,还可以实现这个mixin类型,以表明它提供了某些可供选择的行为。抽象类不能用于定义mixin。
通过对你导出的每个接口都提供一个抽象的骨架实现类,把接口和抽象类的优点结合起来。如AbstractCollection、AbstractSet等。骨架实现的美妙之处在于,它们为抽象提供了实现上的帮助,但又不强加“抽象类被用作类型定义时”所特有的严格限制。
使用抽象类来定义允许多个实现的类型,与使用接口相比有一个明显的优势:抽象类的演变比接口的演变要容易得多。在抽象类中增加新方法并且实现后在所有的子类实现都将提供这个新的方法。对于接口这样做是行不通的。
因此,设计公有的接口要非常谨慎,接口一旦被公开发行,并且已被广泛实现,再想改变这个接口几乎是不可能的。
总结,接口通常是定义允许多个实现的类型的最佳途径。这条规则有个例外,即当演变的容易性比灵活性和功能更为重要的时候,在这种情况下,应该使用抽象类来定义类型,但前提是必须理解并且可以接受这些局限性。如果你导出了一个重要的接口,就应该坚决考虑同时提供骨架实现类。最后,应该尽可能谨慎地设计所有的公有接口,并通过编写多个实现来对它们进行全面的测试。
7. 接口只用于定义类型
当类实现接口时,接口就充当可以引用这个类的实例的类型(type)。因此类实现了接口,就表明客户端可以对这个类的实例实施某些动作。为了其他目的而使用接口是不恰当的。但除了常量接口(没有包含任何方法,只包含静态final域),使用这些常量的类实现这个接口,以避免用类名来修改常量名。
常量接口模式是对接口的不良使用。如果这些常量最好被看作枚举类型的成员,就应该使用枚举类型。否则,应该使用不可实例化的工具类来导出这些常量。工具类通常要求客户端用类名来修饰这些常量名。也可以使用静态导入,避免用类名修饰常量名
总结,接口应该只被用来定义类型,不应该被用来导出常量。
8. 类层次优于标签类
标签类是指类中包含了表示实例风格的标签域,它们中充斥着枚举声明,标签域以及条件语句。标签类过于冗长、容易出错,并且效率低下。如下:
// Tagged class - vastly inferior to a class hierarchy!
class Figure {
enum Shape { RECTANGLE, CIRCLE };
// Tag field - the shape of this figure
final Shape shape;
// These fields are used only if shape is RECTANGLE
double length;
double width;
// This field is used only if shape is CIRCLE
double radius;
// Constructor for circle
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// Constructor for rectangle
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch(shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError();
}
}
}
替换标签类更好的方法是子类型化,标签类正是类层次的一种简单的仿效。
为了将标签类转变成类层次,首先要为标签类中的每个方法都定义一个包含抽象方法抽象类,这每个访求的行为都依赖于标签值。接下来,为每种原始标签类都定义根类的具体子类。上面的类更改如下:
// Class hierarchy replacement for a tagged class
abstract class Figure {
abstract double area();
}
class Circle extends Figure {
final double radius;
Circle(double radius) { this.radius = radius; }
double area() { return Math.PI * (radius * radius); }
}
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() { return length * width; }
}
class Square extends Rectangle {
Square(double side) {
super(side, side);
}
}
这个类层次纠正了前面提到过的标签类的所有缺点。它们可以用来反映类型之间本质上的层次关系,有助于增强灵活性,并进行更好的编译时类型检查。
标签类很少有适用的时候。当你想要编写一个包含显示标签域类时,应该考虑一下,这个标签是否可以被取消,这个类是否可以用类层次来代替。当你遇到一个包含标签域的现有类时,就要考虑将它重构到一个层次结构中去。
9. 用函数对象表示策略
某些机制用于允许函数的调用者通过传入第二个函数,来指定自己的行为。这种机制被称为策略模式。
Java没有提供函数指针,但是可以用对象引用实现同样的功能。调用对象上的方法通常是执行该对象上某项操作,然而我们也可能定义这样一种对象,它的方法执行其他对象(这些对象被显式传递给这些方法)上的操作。如果一个类仅仅导出这样的一个方法,它的实例实际上就等同于一个指向该方法的指针。这样的实例被称为函数对象。如下:
class StringLengthComparator {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
指向StringLengthComparator对象的引用可以被当做是一个指向该比较器的“函数指针”,可以在任意一对象字符串上被调用。换句话说,StringLengthComparator实例是用于字符串比较操作的具体策略。
作为典型的具体策略类,StringLengthComparator类是无状态的,他没有域,所以这个类的所有实例在功能功能上都是等价的。因此,他作为Singleton是非常合适的,可以节省不必要的对象创建开销。
class StringLengthComparator2 {
private StringLengthComparator2(){}
public static final StringLengthComparator2 INSTANCE = new StringLengthComparator2();
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
为了客户端能传递任何其他的比较策略,我们需要定义一个策略接口,如下:
public interface Comparable<T> {
public int compareTo(T t1, T t2);
}
class StringLengthComparator2 implements Comparable<String>{ ... }
具体的策略类往往使用匿名类声明,如下:
Arrays.sort(stringArray,new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return s1.length() - s2.length();
}
});
但是,这样每次执行调用的时候都创建一个新的实例。如果他被重复执行,考虑将实例对象存储到一个私有的静态final域里,并重用他:
public class Host {
private static class StrLenCmp implements Comparator<String>, Serializable {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();
}
函数指针的主要用途就是实现策略(Strategy)模式。为了在Java中实现这种模式,要声明一个接口来表示该策略,并且为每个且体策略声明一个实现了该接口的类。当一个具体策略只被使用一次时,通常使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计用来重复使用的时候,它的类通常就要被实现为私有的静态成员类,并通过公有的静态final域被导出,其类型为该策略接口。
10. 优先考虑静态成员类(嵌套类)
嵌套类(nested class)是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为他的外围类(enclosing class)提供服务。
如果嵌套类将来可能会用于其他的某个环境中,他就应该是顶层类(top-level class)。 嵌套类有四种:静态成员类(static member class)、非静态成员类(nonstatic member class)、匿名类(anonymous class)和局部类(local class)。 除了第一种之外,其他三种都称为内部类(inner class)。
静态成员类是最简单的一种嵌套类,最好把它看作是普通的类,只是碰巧被声明在另一个类的内部而已,它可以访问外围类的所有成员,包括那些声明为私有的成员。静态成员类是外围类的一个静态成员,与其他的静态成员一样,也遵守同样的可访问性规则。静态成员类的一种常见用法是作为公有的辅助类,仅当与它的外部类一起使用时才有意义。
从语法上讲,静态成员类和非静态成员类之间的唯一区别是,静态成员类的声明中包含修饰符static。尽管他们的语法非常相似,但是两种嵌套类有很大的不同。 非静态成员类的每个实例都隐含着与外围类的一个外围实例(enclosing instance)相关联。在非静态成员类的实例方法内部,可以调用外围实例上的方法,或者利用修饰过的this构造获得外围实例的引用。 如果嵌套类的实例可以在他外围类的实力之外独立存在,这个嵌套类就必须是静态成员类:在没有外围实例的情况下,要想创建非静态成员类的实例是不可能的。
当非静态成员类的实例被创建的时候,他和外围之间的关联关系也随之被建立起来;而且,这种关联关系以后不能被修改。通常情况下,当在外围类的某个实例方法的内部调用非静态成员类的构造器时,这种关系被自动建立起来。 使用表达式enclosingInstance.new MemberClass(args)来手工建立这种关联关系也是有可能的,但是很少使用。正如你所预料的那样,这种关联关系需要消耗费静态成员类的实例空间,并且增加了构造的时间开销。
非静态成员类的一种常见用法是定义一个Adapter,它允许外部类的实例被看作是另一个不相关类的实例。
如果声明成员类不要求访问外围实例,就要始终把static修饰符放在他的声明中,使它成为静态成员类。如果省略了static修饰符,则每个实例都将包含一个额外的指向外围对象的引用。保存这份引用要消耗时间和空间。并且会导致外围实例在符合垃圾回收时却仍然得以保留。如果在没有外围实例的情况下,也需要分配实例,就不能使用非静态成员类,因为非静态成员类的实例必须要有一个外围实例。
私有静态成员类的一种常见用法是用来代表外围类所代表的对象的组件。
匿名类不同于Java程序设计语言中的其他任何语法单元,正如你所想象的,匿名类没有名字。它不是处置类的一个成员,它并不与其他的成员一起被声明,而是在使用的同时被声明和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。当且仅当匿名类出现在非静态的环境中,它才有外围实例。但是即使它们出现在静态的环境中,也不可能拥有任何静态成员。
匿名类除了在它们被声明的时候之外,是无法将它们实例化的。你不能执行instanceof测试,或者做任何需要命名类的其他事情。你无法声明一个匿名类来实现多个接口,或者扩展一个类。匿名类的客户端无法调用任何成员,除了从它的超类型中继承得到之外。由于匿名类出现在表达式中,它们必须保持简短(大约10行或者更少),否则会影响程序的可读性。
匿名类的一种常见用法是动态的创建函数对象(如上一知识点)。 另一种常见用法是创建过程对象(如Runnable、Thread或TimerTask实例)。 第三种常见的用法是在静态工厂方法的内部。
局部类是四种嵌套类中用得最少的类。在任何可以声明局部变量的地方,都可以声明局部类。与匿名类一样,它们必须非常简短,以便不会影响可读性。
简而言之,如果一个嵌套类需要在单个方法之外仍然是可见的,或者他太长了,不适合方法内部,就应该使用成员类。如果成员类的每个实例都需要一个指向其外围实例的引用,就要把成员类做成非静态的;否则就做成静态的。假设这个嵌套类属于一个方法的内部,如果你只需要在一个地方创建实例,并且已经有了一个预置的类型可以说明这个类的特征,就要把他做成匿名类;否则,就做成局部类。
1. 使类和成员的可访问性最小化
2. 在公有类中使用访问方法而非公有域
3. 使可变性最小化
4. 复合优先于继承
5. 要么为继承而设计,并提供文档说明,要么就禁止继承
6. 接口优于抽象类
7. 接口只用于定义类型
8. 类层次优于标签类
9. 用函数对象表示策略
10. 优先考虑静态成员类(嵌套类)
1. 使类和成员的可访问性最小化
设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离开来。然后,模块之间只通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况。这个概念被称为信息隐藏或封装,是软件设计的基本原则之一。信息隐藏可以有效地解除组成系统的各模块之间的耦合关系,使得这些模块可以独立的开发、测试、优化、使用、理解和修改。
Java程序设计语言提供了许多机制来协助信息隐藏。访问控制机制决定了类、接口和成员的可访问性,实体的可访问性是由该类实体声明所在的位置以及该实体声明中所出现的访问修饰符(private、protected、public)共同决定的。规则是尽可能地使每个类或者成员不被外界访问,换句话说应该使用与你正在编写的软件功能一致、尽可能最小的访问级别。
访问级别:
私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员。
包级私有的:声明该成员的包内部的任何类都可以访问这个成员,缺省的访问级别。
受保护的:声明该成员的类的子类和该成员的包内部的类可以访问这个成员。
公有的:在任何地方都可以访问该成员。
(1)如果类或者接口能够做成包级私有的,它就应该被做成包级私有。这样在以后如果需要进行修改、替换、或者删除无需担心会影响到现有的客户端程序。如果你把它做成公有的,你就有责任永远支持它,以保持它们的兼容性。
(2)如果一个类只是在某一个类的内部被用到,就应该考虑使它成为那个类的私有嵌套类。
(3)如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。如果一个类实现了一个接口,那么接口中所有的类方法在这个类中也都必须被声明为公有的,因为接口中的所有方法都隐含着公有访问级别。
(4)不能为了测试,而将类、接口或者成员变成包的导出的API的一部分。可以让测试作为被测试的包的一部分来支行,从而能够访问它的包级私有的元素。
(5)实例域决不能是公有的。如果域是final的,或者是一个指向可变对象的final引用,那么一旦使这个域成为公有的,就放弃了对存储在这个域中的值进行限制的能力。这意味着,你也放弃了强制这个域不可变的能力。同时,当这个域被修改的时候,你也失去了对它采取任何行动的能力。因此包含公有可变域的类并不是线程安全的。
(6)长度非零的数组总是可变的,所以类具有公有的静态final数组域和返回这种域的方法都是错误的,如果类具有这样的域或者访问方法,客户端将能够修改数组中的内容。
public static final Thing[] VALUES={....};
许多IDE会产生返回指向私有数组域的引用的访问方法,这样就会产生上面的问题。修正这个问题有两种方法:
一是使公有数组变成私有的,并增加一个公有的不可变列表。
二是使数组变成私有的,并添加一个公有方法,它返回私有数组的一个备份。
总而言之,你应该始终尽可能地降低可访问性。在设计了一个最小的公有API之后,应该防止把任何散乱的类、接口和成员变成API的一部分。除了公有静态final域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都是不可变的。
2. 在公有类中使用访问方法而非公有域
如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改变该类的内部表示法的灵活性。
class Point{
private int a;
private int b;
public Point(int a, int b){
this.x = y;
this.y = y;
}
public int getA(){return a;}
public int getB(){return b;}
public void setA(int a){this.a = a;}
public void setB(int b){this.b = b;}
}
如果类是包级私有的,或者是私有的嵌套类,直接暴露它的数据域并没有本质的错误。如有必要,不改变包之外的任何代码而只改变内部数据表示法也是可以的。
让公有类直接暴露域虽然从来都不是种好办法,但是如果域是不可变的,这种做法的危害就比较小一些。
3. 使可变性最小化
不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。不可变类比可变类更加易于设计、实现和使用。它们不容易出错,且更加安全。如String、基本类型的包装类、BigInteger、BigDecimal等。
为了使类成为不可变,要遵循下面五条规则:
(1)不要提供任何会修改对象状态的方法(mutator)
(2)保证类不会被扩展,防止子类假装对象的状态已经改变,从而破坏该类的不可变行为。一般防止子类化就是使这个类成为final的。
(3)使所有的域都是final的,或者让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂来代替公有的构造器
(4)使所有的域都成为私有的,防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象
(5)确保对于任何可变组件的互斥访问
不可变对象本质上是线程安全的,它们不要求同步。不可变类应该充分利用这种优势,鼓励客户端尽可能地重用现有的实例,要做到这一点,一个很简便的办法就是对于频繁用到的值,为它们提供公有的静态final常量,
public static final String ERROR_TYPE = "xxx";
这种方法可以被进一步扩展,不可变的类可以提供一些静态工厂,把频繁被请求的实例缓存起来,从而当现有实例可以符合请求的时候,就不必创建新的实例。
不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。
如果能够精确地预测出客户端将要在不可变的类上执行哪些复杂的多阶段操作, 可以提供一种包级私有的可变配套类,如String类的可变配套类StringBuilder。
坚决不要为每个get方法编写一个相应的set方法,除非有很多的理由要让类成为可变的类,否则就应该是不可变的。
4. 复合优先于继承
继承是实现代码重用的有力手段,但它并非永远是完成这项工作的最佳工具。使用不当会导致软件变得脆弱。在包的内部使用继承是非常安全的,在这里子类和超类的实现都处在同一个程序员的控制之下。对于专门为了继承而设计、并且具有很好的文档说明的类来说,使用继承也是非常安全的。
继承打破了封装性,换句话说,子类依赖于其超类中特定功能的实现细节,如果超类的实现随着版本的不同而有所变化,子类可能会遭到破坏,即使子类的代码完全没有改变。因而子类必须要跟着其超类的更新而演变。
幸运的是,有一种办法可以避免前面提到的所有问题。不用扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例。这种设计被称做“复合”,因为现有的类变成了新类的一个组件。新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果,这被称为转发,新类中的方法被称为转发方法。这样得到的类将会非常稳固,它不依赖于现有类的实现细节,即使现有的类添加了新的方法,也不会影响新的类。新类被称为包装类,这也正是Decorator模式,注意包装类不适合用在回调框架中。实践中包装类不会对性能造成很大的影响。
只有当子类真正是超类的子类型时,才适合用继承。即是说只有当两者之间确实存在“is-a”关系的时候,一个类才能扩展另一个类。
5. 要么为继承而设计,并提供文档说明,要么就禁止继承
首先,该类的文档必须精确地描述覆盖每个方法带来的影响。换句话说,该类必须有文档说明它可覆盖的方法的自用性。即对于每个公有的或受保护的方法或者构造器,它的文档必须指明该方法或者构造器调用了哪些可覆盖的方法,是以什么顺序调用的,每个调用的结果又是如何影响后续的处理过程的(所谓可覆盖的方法是指非final的,公有的或受保护的)。如果方法调用到了可覆盖的方法,在它的文档注释的末尾应该包含关于这些调用的描述信息。
为了能编写出更加有效的子类,类必须通过某种形式提供适当的钩子(hook),以便能够进入到它的内部工作流程中,这种形式可以是精心选择的受保护的方法,也可以是受保护的域。
构造器决不能调用可被覆盖的方法,无论是直接调用还是间接调用。
如果类是为了继承而被设计的,实现Cloneable或Serializable接口都不是个好注意,因为它们把一些实质性的负担转嫁到了扩展这个类的程序员的身上。当然可以采取一些特殊的手段使得子类实现这些接口(在谨慎的覆盖clone和谨慎的实现Serialable接口两节中有说到),即无论是clone还是readObject方法都不可以调用可覆盖的方法,不管是直接还是间接的方式。如果你决定实现Serializable接口,并且该类有一个readResolve或者writeReplace方法,就必须使readResolve或者writeReplace成为受保护的方法,而不是私有的方法,否则子类将会不声不响的忽略掉这两个方法。
对于那些并非为了安全地进行子类化而设计和编写文档的类,要禁止子类化。禁止子类化一种方法就是把类声明为final的,另一种方法就是把所有的构造器都变成私有的,或者包级私有的,并增加一些公有的静态工厂来替代构造器。
6. 接口优于抽象类
这两者机制之间最明显的区别在于,抽象类允许包含某些方法的实现,但是接口则不允许,接口则不允许。另一个重要的区别在于定义的类必须成为抽象类的一个子类,而java只允许单继承,故抽象类作为类型定义受到了极大的限制。
接口是定义mixin(增加功能混合到类型的主要功能上,如Comparable)的理想选择。mixin是指这样的类型:它除了实现它的“基本类型”之外,还可以实现这个mixin类型,以表明它提供了某些可供选择的行为。抽象类不能用于定义mixin。
通过对你导出的每个接口都提供一个抽象的骨架实现类,把接口和抽象类的优点结合起来。如AbstractCollection、AbstractSet等。骨架实现的美妙之处在于,它们为抽象提供了实现上的帮助,但又不强加“抽象类被用作类型定义时”所特有的严格限制。
使用抽象类来定义允许多个实现的类型,与使用接口相比有一个明显的优势:抽象类的演变比接口的演变要容易得多。在抽象类中增加新方法并且实现后在所有的子类实现都将提供这个新的方法。对于接口这样做是行不通的。
因此,设计公有的接口要非常谨慎,接口一旦被公开发行,并且已被广泛实现,再想改变这个接口几乎是不可能的。
总结,接口通常是定义允许多个实现的类型的最佳途径。这条规则有个例外,即当演变的容易性比灵活性和功能更为重要的时候,在这种情况下,应该使用抽象类来定义类型,但前提是必须理解并且可以接受这些局限性。如果你导出了一个重要的接口,就应该坚决考虑同时提供骨架实现类。最后,应该尽可能谨慎地设计所有的公有接口,并通过编写多个实现来对它们进行全面的测试。
7. 接口只用于定义类型
当类实现接口时,接口就充当可以引用这个类的实例的类型(type)。因此类实现了接口,就表明客户端可以对这个类的实例实施某些动作。为了其他目的而使用接口是不恰当的。但除了常量接口(没有包含任何方法,只包含静态final域),使用这些常量的类实现这个接口,以避免用类名来修改常量名。
常量接口模式是对接口的不良使用。如果这些常量最好被看作枚举类型的成员,就应该使用枚举类型。否则,应该使用不可实例化的工具类来导出这些常量。工具类通常要求客户端用类名来修饰这些常量名。也可以使用静态导入,避免用类名修饰常量名
总结,接口应该只被用来定义类型,不应该被用来导出常量。
8. 类层次优于标签类
标签类是指类中包含了表示实例风格的标签域,它们中充斥着枚举声明,标签域以及条件语句。标签类过于冗长、容易出错,并且效率低下。如下:
// Tagged class - vastly inferior to a class hierarchy!
class Figure {
enum Shape { RECTANGLE, CIRCLE };
// Tag field - the shape of this figure
final Shape shape;
// These fields are used only if shape is RECTANGLE
double length;
double width;
// This field is used only if shape is CIRCLE
double radius;
// Constructor for circle
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// Constructor for rectangle
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch(shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError();
}
}
}
替换标签类更好的方法是子类型化,标签类正是类层次的一种简单的仿效。
为了将标签类转变成类层次,首先要为标签类中的每个方法都定义一个包含抽象方法抽象类,这每个访求的行为都依赖于标签值。接下来,为每种原始标签类都定义根类的具体子类。上面的类更改如下:
// Class hierarchy replacement for a tagged class
abstract class Figure {
abstract double area();
}
class Circle extends Figure {
final double radius;
Circle(double radius) { this.radius = radius; }
double area() { return Math.PI * (radius * radius); }
}
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() { return length * width; }
}
class Square extends Rectangle {
Square(double side) {
super(side, side);
}
}
这个类层次纠正了前面提到过的标签类的所有缺点。它们可以用来反映类型之间本质上的层次关系,有助于增强灵活性,并进行更好的编译时类型检查。
标签类很少有适用的时候。当你想要编写一个包含显示标签域类时,应该考虑一下,这个标签是否可以被取消,这个类是否可以用类层次来代替。当你遇到一个包含标签域的现有类时,就要考虑将它重构到一个层次结构中去。
9. 用函数对象表示策略
某些机制用于允许函数的调用者通过传入第二个函数,来指定自己的行为。这种机制被称为策略模式。
Java没有提供函数指针,但是可以用对象引用实现同样的功能。调用对象上的方法通常是执行该对象上某项操作,然而我们也可能定义这样一种对象,它的方法执行其他对象(这些对象被显式传递给这些方法)上的操作。如果一个类仅仅导出这样的一个方法,它的实例实际上就等同于一个指向该方法的指针。这样的实例被称为函数对象。如下:
class StringLengthComparator {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
指向StringLengthComparator对象的引用可以被当做是一个指向该比较器的“函数指针”,可以在任意一对象字符串上被调用。换句话说,StringLengthComparator实例是用于字符串比较操作的具体策略。
作为典型的具体策略类,StringLengthComparator类是无状态的,他没有域,所以这个类的所有实例在功能功能上都是等价的。因此,他作为Singleton是非常合适的,可以节省不必要的对象创建开销。
class StringLengthComparator2 {
private StringLengthComparator2(){}
public static final StringLengthComparator2 INSTANCE = new StringLengthComparator2();
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
为了客户端能传递任何其他的比较策略,我们需要定义一个策略接口,如下:
public interface Comparable<T> {
public int compareTo(T t1, T t2);
}
class StringLengthComparator2 implements Comparable<String>{ ... }
具体的策略类往往使用匿名类声明,如下:
Arrays.sort(stringArray,new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return s1.length() - s2.length();
}
});
但是,这样每次执行调用的时候都创建一个新的实例。如果他被重复执行,考虑将实例对象存储到一个私有的静态final域里,并重用他:
public class Host {
private static class StrLenCmp implements Comparator<String>, Serializable {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();
}
函数指针的主要用途就是实现策略(Strategy)模式。为了在Java中实现这种模式,要声明一个接口来表示该策略,并且为每个且体策略声明一个实现了该接口的类。当一个具体策略只被使用一次时,通常使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计用来重复使用的时候,它的类通常就要被实现为私有的静态成员类,并通过公有的静态final域被导出,其类型为该策略接口。
10. 优先考虑静态成员类(嵌套类)
嵌套类(nested class)是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为他的外围类(enclosing class)提供服务。
如果嵌套类将来可能会用于其他的某个环境中,他就应该是顶层类(top-level class)。 嵌套类有四种:静态成员类(static member class)、非静态成员类(nonstatic member class)、匿名类(anonymous class)和局部类(local class)。 除了第一种之外,其他三种都称为内部类(inner class)。
静态成员类是最简单的一种嵌套类,最好把它看作是普通的类,只是碰巧被声明在另一个类的内部而已,它可以访问外围类的所有成员,包括那些声明为私有的成员。静态成员类是外围类的一个静态成员,与其他的静态成员一样,也遵守同样的可访问性规则。静态成员类的一种常见用法是作为公有的辅助类,仅当与它的外部类一起使用时才有意义。
从语法上讲,静态成员类和非静态成员类之间的唯一区别是,静态成员类的声明中包含修饰符static。尽管他们的语法非常相似,但是两种嵌套类有很大的不同。 非静态成员类的每个实例都隐含着与外围类的一个外围实例(enclosing instance)相关联。在非静态成员类的实例方法内部,可以调用外围实例上的方法,或者利用修饰过的this构造获得外围实例的引用。 如果嵌套类的实例可以在他外围类的实力之外独立存在,这个嵌套类就必须是静态成员类:在没有外围实例的情况下,要想创建非静态成员类的实例是不可能的。
当非静态成员类的实例被创建的时候,他和外围之间的关联关系也随之被建立起来;而且,这种关联关系以后不能被修改。通常情况下,当在外围类的某个实例方法的内部调用非静态成员类的构造器时,这种关系被自动建立起来。 使用表达式enclosingInstance.new MemberClass(args)来手工建立这种关联关系也是有可能的,但是很少使用。正如你所预料的那样,这种关联关系需要消耗费静态成员类的实例空间,并且增加了构造的时间开销。
非静态成员类的一种常见用法是定义一个Adapter,它允许外部类的实例被看作是另一个不相关类的实例。
如果声明成员类不要求访问外围实例,就要始终把static修饰符放在他的声明中,使它成为静态成员类。如果省略了static修饰符,则每个实例都将包含一个额外的指向外围对象的引用。保存这份引用要消耗时间和空间。并且会导致外围实例在符合垃圾回收时却仍然得以保留。如果在没有外围实例的情况下,也需要分配实例,就不能使用非静态成员类,因为非静态成员类的实例必须要有一个外围实例。
私有静态成员类的一种常见用法是用来代表外围类所代表的对象的组件。
匿名类不同于Java程序设计语言中的其他任何语法单元,正如你所想象的,匿名类没有名字。它不是处置类的一个成员,它并不与其他的成员一起被声明,而是在使用的同时被声明和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。当且仅当匿名类出现在非静态的环境中,它才有外围实例。但是即使它们出现在静态的环境中,也不可能拥有任何静态成员。
匿名类除了在它们被声明的时候之外,是无法将它们实例化的。你不能执行instanceof测试,或者做任何需要命名类的其他事情。你无法声明一个匿名类来实现多个接口,或者扩展一个类。匿名类的客户端无法调用任何成员,除了从它的超类型中继承得到之外。由于匿名类出现在表达式中,它们必须保持简短(大约10行或者更少),否则会影响程序的可读性。
匿名类的一种常见用法是动态的创建函数对象(如上一知识点)。 另一种常见用法是创建过程对象(如Runnable、Thread或TimerTask实例)。 第三种常见的用法是在静态工厂方法的内部。
局部类是四种嵌套类中用得最少的类。在任何可以声明局部变量的地方,都可以声明局部类。与匿名类一样,它们必须非常简短,以便不会影响可读性。
简而言之,如果一个嵌套类需要在单个方法之外仍然是可见的,或者他太长了,不适合方法内部,就应该使用成员类。如果成员类的每个实例都需要一个指向其外围实例的引用,就要把成员类做成非静态的;否则就做成静态的。假设这个嵌套类属于一个方法的内部,如果你只需要在一个地方创建实例,并且已经有了一个预置的类型可以说明这个类的特征,就要把他做成匿名类;否则,就做成局部类。
发表评论
-
EffectiveJava--序列化
2014-12-03 11:43 1596本章内容: 1. 谨慎地实 ... -
EffectiveJava--并发
2014-12-01 21:53 1767本章内容: 1. 同步访问共享的可变数据 2. 避免过度同步 ... -
EffectiveJava--异常
2014-11-23 21:47 1104本章内容: 1. 只针对异 ... -
EffectiveJava--通用程序设计
2014-11-20 16:22 811本章内容: 1. 将局部变 ... -
EffectiveJava--方法
2014-11-16 17:44 1373本章内容: 1. 检查参数的有效性 2. 必要时进行保护性拷贝 ... -
EffectiveJava--枚举和注解
2014-11-15 21:23 1671本章内容: 1. 用enum代� ... -
EffectiveJava--泛型
2014-11-08 00:22 1262本章内容: 1. 请不要在 ... -
EffectiveJava--对象通用方法
2014-10-27 22:49 948本章内容: 1. 覆盖equals ... -
EffectiveJava--创建和销毁对象
2014-10-22 23:38 1002本章内容: 1. 考虑用静 ... -
Java多线程
2014-04-28 18:56 1080一、多线程简介 对于Java而言,可以在一个程序中并发地启 ... -
Java异常处理
2014-04-27 00:49 1468一、异常简介 在程序运行过程中,如果环境检测出一个不可以执 ... -
Java集合框架
2014-04-24 23:56 1894一、集合框架简介 数据结构是以某种形式将数据组织在一起的集 ... -
Java编程规范整理
2014-03-29 21:36 1597一、排版1. 代码采用缩进风格编写,缩进空格数为4,不允许 ...
相关推荐
内容概要:本文将金属腐蚀现象比作游戏角色受到持续伤害(debuff),并采用浓度迁移和损伤方程来建模这一过程。文中首先介绍了浓度迁移的概念,将其比喻为游戏中使角色持续掉血的毒雾效果,并展示了如何利用Numpy矩阵存储浓度场以及通过卷积操作实现浓度扩散。接着引入了损伤方程,用于评估材料随时间累积的损伤程度,同时考虑到材料自身的抗性特性。作者还提供了完整的Python代码示例,演示了如何在一个二维网格环境中模拟24小时内金属表面发生的腐蚀变化,最终得到类似珊瑚状分形结构的腐蚀形态。此外,文章提到可以通过调整模型参数如腐蚀速率、材料抗性等,使得模拟更加贴近实际情况。 适合人群:对材料科学、物理化学感兴趣的科研工作者和技术爱好者,尤其是那些希望通过编程手段深入理解金属腐蚀机制的人群。 使用场景及目标:适用于希望借助数值模拟方法研究金属腐蚀行为的研究人员;可用于教学目的,帮助学生更好地掌握相关理论知识;也可作为工程项目前期评估工具,预测不同条件下金属构件可能遭受的腐蚀损害。 阅读建议:由于文中涉及较多数学公式和编程细节,建议读者具备一定的Python编程基础以及对线性代数有一定了解。对于想要进一步探索该领域的学者来说,可以尝试修改现有代码中的参数设置或者扩展模型维度,从而获得更丰富的研究成果。
斐讯丽江大数据产业园项目土石方平场工程开挖施工方案改.doc
无线通信发展历程.doc
教务管理系统软件项目计划任务书.doc
数据库系统概论培训课件.pptx
内容概要:本文详细介绍了信捷XC3 PLC与三台西门子V20变频器之间的通讯程序,涵盖硬件连接、参数设置、PLC程序核心代码以及触摸屏程序。文中提供了详细的接线方法、参数配置步骤、通讯地址说明,并分享了实际操作中的经验和常见问题解决方案。程序采用轮询方式,确保通讯稳定可靠,适用于工业自动化生产线。 适合人群:从事工业自动化领域的工程师和技术人员,特别是对PLC和变频器通讯感兴趣的读者。 使用场景及目标:①帮助工程师快速搭建稳定的PLC与变频器通讯系统;②提供详细的参数设置指导,避免常见的配置错误;③分享实际项目经验,提高系统的稳定性和可靠性。 其他说明:本文不仅提供了理论知识,还包含了大量实战经验和技巧,如终端电阻的正确使用、频率设定的注意事项等,有助于读者更好地理解和应用相关技术。
新版计算机试题库.doc
基于截断核范数张量鲁棒主成分分析.zip
基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目),个人经导师指导并认可通过的高分设计项目,评审分98分,项目中的源码都是经过本地编译过可运行的,都经过严格调试,确保可以运行!主要针对计算机相关专业的正在做大作业、毕业设计的学生和需要项目实战练习的学习者,资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载使用。 基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(高分项目)基于机器学习的银行客户产品认购预测项目Python源码及全套资料(
内容概要:本文介绍了一款基于C#的标签打印控制系统,旨在为企业提供灵活、高效的标签打印解决方案。系统不仅支持标签的编辑,还能够自动生成条形码和二维码,并允许用户自定义其位置和样式。此外,系统集成了自动打印功能,确保高质量的标签输出。该系统的模块化设计使其易于二次开发和扩展,适用于多个行业的应用场景,如物流、零售和生产线管理。文中详细介绍了系统的架构、核心功能以及实际应用案例,展示了其实用性和灵活性。 适合人群:对C#有一定了解的技术人员,尤其是需要开发或改进标签打印系统的开发人员。 使用场景及目标:① 需要在现有系统中集成标签打印功能的企业;② 希望定制化标签打印解决方案的中小企业;③ 对条形码和二维码有特殊需求的应用场景。 阅读建议:本文提供了详细的系统架构和技术实现细节,建议读者结合自身需求,重点关注二次开发和扩展部分,以便更好地利用该系统解决实际问题。
小区物业管理系统是一款基于.NET平台开发的软件应用,用于全面管理住宅小区的日常运营。它通过多种功能提升物业管理效率、优化服务质量,并促进业主与物业之间的沟通。在设计过程中,该系统采用了UML(统一建模语言)来确保其结构化和可维护性。UML是一种标准化的建模工具,通过图形化方式描述系统的结构与行为,帮助开发者理解和实现复杂的软件项目。 本项目涵盖了UML的十大模型图,包括用例图、类图、对象图、序列图、协作图、状态图、活动图、组件图、部署图和包图。这些模型图从不同角度描绘系统,例如用例图展示参与者(如业主、物业人员)与系统功能的交互;类图定义系统中的类、接口及其关系;对象图是类图的实例;序列图和协作图描述对象间的动态交互;状态图和活动图关注行为变化;组件图和部署图关注物理结构;包图则用于组织模块结构。 压缩包中的“杨平.doc”可能是设计者或项目负责人杨平的工作文档,包含项目需求、设计思路等重要信息。“任务书.doc”应明确项目的具体任务要求,如功能需求和性能指标。“小区物业管理系统.mdl”是UML模型文件,记录了系统的详细设计。“小区物业”可能是其他相关文件,如源代码或数据库脚本。整个项目提供了从需求分析到系统实现的完整流程,对于学习.NET开发和理解UML建模技术具有重要参考价值。开发者通过研究这些模型图,能够更好地构建类似的物业管理系统,提升软件工程实践能力。
内容概要:本文详细介绍了基于TMS320F28069的伺服驱动器设计方案及其实际应用。首先,文章展示了完整的硬件设计,包括控制板、驱动板、电源板和滤波板的具体构造和功能特点。其次,深入探讨了控制源代码的关键部分,如PWM配置、磁场定向控制(FOC)算法以及ADC校准等。文中还特别提到了一些优化技巧,如降低开关损耗、减少寄生电感和提高电磁兼容性的具体措施。最后,强调了该方案的实际量产经验和调试过程中遇到的问题及解决方案。 适合人群:具有一定嵌入式系统和电机控制基础的研发人员和技术爱好者。 使用场景及目标:适用于希望深入了解TMS320F28069在伺服驱动器中的应用,掌握其硬件设计和软件编程技巧的人群。目标是帮助读者理解并能够独立开发类似的伺服控制系统。 其他说明:由于代码注释较少,建议初学者先从官方例程入手,逐步过渡到复杂项目的学习。同时,为了更好地理解和调试,推荐配备必要的测试设备如红外热像仪和逻辑分析仪。
内容概要:本文详细介绍了电子凸轮追剪曲线生成算法的原理、应用以及在麦格米特品牌下的实现方法。电子凸轮追剪曲线生成算法主要用于控制机械设备的运动轨迹,在自动化生产线中尤其重要。文中解释了该算法的数学基础和编程逻辑,展示了如何通过确定追剪曲线的形状和运动轨迹来生成电子凸轮的运动指令。此外,还提供了麦格米特品牌的PLC编程环境和工具的具体实现步骤,并附有简化的伪代码示例。 适合人群:从事自动化设备研发的技术人员、机械工程师、PLC程序员。 使用场景及目标:适用于需要精确控制机械设备运动轨迹的场合,如剪切、冲压等工艺。目标是帮助技术人员理解和实现电子凸轮追剪曲线生成算法,从而提升生产效率和产品质量。 其他说明:随着自动化技术的发展,该算法有望进一步优化并应用于更多的工业场景。
内容概要:本文详细介绍了如何在MATLAB环境中使用遗传算法(GA)求解函数极值问题。文中采用了实数编码方式、非均匀变异算子和轮盘赌选择法,提供了完整的MATLAB代码及详细注释,帮助初学者快速理解和应用遗传算法。首先解释了遗传算法的基础概念,如实数编码、非均匀变异和轮盘赌选择法的作用和意义。接着展示了具体的MATLAB代码实现步骤,包括函数定义、参数设置、种群初始化、适应度计算、选择、交叉和变异操作,最后输出最优解。 适合人群:对遗传算法感兴趣的初学者,尤其是有一定MATLAB基础的学生和研究人员。 使用场景及目标:适用于需要解决一元或多维函数极值问题的研究和工程应用。通过本教程的学习,读者可以掌握遗传算法的基本原理和实现方法,能够独立编写简单的遗传算法程序。 其他说明:本文不仅提供理论讲解,还附带完整代码和详细注释,便于读者理解和实践。建议读者跟随代码逐步运行,加深对遗传算法的理解。
内容概要:本文详细介绍了MATLAB Simulink在车辆运动学仿真中的应用,重点探讨了实时位置和车身姿态的仿真过程。首先,文章简述了Simulink作为强大仿真工具的特点及其在车辆运动学建模中的优势。接着,分别从模型搭建、仿真参数设置和结果展示三个方面详细讲解了实时位置仿真的具体步骤。对于车身姿态仿真,则着重于模型构建、边界条件设置及仿真结果解读,展示了车辆在不同速度和转向角下的姿态变化情况。最后,文章指出通过Simulink进行的仿真分析能够为车辆控制、安全驾驶等领域提供技术支持,并有助于提升车辆行驶的安全性和稳定性。 适合人群:对车辆工程、自动化控制感兴趣的科研人员和技术开发者。 使用场景及目标:适用于希望深入了解车辆运动学仿真原理的研究者,以及希望通过仿真手段优化车辆性能的设计工程师。 其他说明:文中不仅提供了理论性的阐述,还结合了具体的实例操作指导,使读者能更好地掌握相关技能并应用于实际项目中。
内容概要:本文介绍了Python实现的UDS通信脚本,该脚本主要针对新能源电动汽车行业,支持Vector CAN和PCAN设备。它不仅能满足ISO15765、ISO14229等上位机代码编写需求,还能用于NXP560xB系列芯片和ARM TLE98x系列底层软件代码编写。此外,脚本还支持标定协议CCP代码编写及ASAP2 A2L文件合成工具的上位机代码编写。脚本具有良好的二次开发扩展能力,适用于多种应用场景。 适合人群:新能源电动汽车行业的资深工程师,尤其是那些从事UDS通信协议、CAN设备开发及相关底层软件代码编写的工程师。 使用场景及目标:① 实现UDS通信协议,支持多种CAN设备;② 编写ISO15765、ISO14229的上位机代码;③ 开发NXP560xB系列芯片和ARM TLE98x系列底层软件;④ 进行标定协议CCP代码编写及ASAP2 A2L文件合成工具的上位机代码编写;⑤ 提供二次开发扩展能力,满足不同应用场景的需求。 其他说明:文中还分享了工程师们在实际工作中积累的经验和技巧,强调了熟悉相关标准和技术规范、熟练掌握编程语言和工具、善于进行二次开发扩展的重要性。
内容概要:本文档系统地介绍了计算机科学多个核心领域的基础知识,涵盖计算机系统基础、数据结构与算法、计算机网络、数据库系统、软件工程、系统架构设计、项目管理、信息安全以及新技术趋势。具体包括计算机组成原理如冯·诺依曼体系结构、操作系统核心机制如进程管理和内存管理;数据结构如线性结构、树与图,经典算法如排序算法和动态规划;计算机网络如OSI与TCP/IP模型、关键协议详解;数据库系统如关系数据库设计和NoSQL;软件工程如开发模型对比、UML建模;系统架构设计如架构模式和性能优化;项目管理如十大知识领域和配置管理;信息安全如密码学基础和攻击与防御;新技术趋势如云计算和大数据与AI。最后还提供了备考策略,包括时间规划和答题技巧。; 适合人群:计算机相关专业学生、初入职场的研发人员或准备相关资格认证考试的考生。; 使用场景及目标:①作为计算机专业课程的学习参考资料;②为备考计算机相关职业资格认证提供系统化的复习指南;③帮助职场新人构建完整的计算机知识体系。; 其他说明:文档内容全面且深入浅出,既适合零散知识点的查漏补缺,也适用于系统的复习备考。建议读者根据自身情况制定合理的阅读计划,重点关注自己薄弱环节的知识点,并结合实际案例进行理解和记忆。
内容概要:本文详细介绍了如何利用C#编写信捷PLC上位机程序,实现与PLC的Modbus TCP通信。文中首先简述了工业自动化背景下PLC与上位机通信的重要性,接着逐步指导读者完成开发环境搭建、Modbus TCP/IP协议栈安装以及关键的C#源代码编写步骤。提供的代码示例展示了从建立TCP连接、初始化Modbus主站对象到最后的数据交互(如读取线圈状态),并且强调了错误处理机制和资源管理的最佳实践。最后提醒使用者依据自身情况调整相关参数以适应不同应用场景的需求。 适合人群:对工业自动化感兴趣的技术爱好者、从事自动化控制系统开发的工程师或者正在学习PLC编程的学生。 使用场景及目标:帮助读者掌握用C#实现信捷PLC上位机的基本流程和技术要点,能够独立构建简单但完整的Modbus TCP通信系统,为进一步深入研究打下坚实的基础。 其他说明:本教程不仅提供了理论知识讲解,还附带了实用的操作指南和完整代码片段,便于快速理解和动手实践。同时鼓励读者在此基础上探索更多高级特性和复杂业务逻辑的实现。
数值分析之幂法及反幂法C语言程序实例.doc
内容概要:本文详细介绍了西门子PLC动态密码程序的设计与实现,特别针对1200PLC和1500PLC型号。该程序主要用于催款业务场景,采用了SCL(结构化控制语言)进行编写,提供了动态密保功能,增强了系统安全性和用户账户的保密性。文章从程序的编写思路、操作步骤到实际应用场景进行了全面解析,强调了其在提升工作效率、减少人为错误以及保障企业信息安全方面的优势。文中还提到,该程序具有高度的通用性和灵活性,适用于不同的业务需求,并配有详细的视频讲解,帮助用户更好地理解和操作。 适合人群:从事工业自动化控制领域的工程师和技术人员,尤其是熟悉或希望深入了解西门子PLC编程的人士。 使用场景及目标:①需要在工业控制系统中实施催款程序的企业;②希望通过增强动态密保功能提升系统安全性的企业;③希望掌握SCL编程技巧并应用于实际项目中的技术人员。 其他说明:该程序不仅提升了系统的安全性和可靠性,还在效率和准确性方面表现出色,为企业带来了更多商业机会和发展空间。