封装是个老生常谈的话题了
那么如何优雅的封装呢?
不要急
本文下面就讲一讲优雅封装的几种方式
如何优雅的封装
一:封装的含义:
封装是一种面向对象编程的概念,指将一个类的内部数据和方法进行保护,使其不能被外部直接访问和修改,而只能通过类的公共方法进行操作。这样可以提高代码的安全性和可维护性。
封装可以分为两个方面:
- 数据封装,即限制外部访问类的私有数据成员,只能通过类的公有接口进行访问;
- 行为封装,即限制外部直接调用类的内部函数,只能通过类的公有接口进行访问。
二:优雅的封装
1. 使用接口和抽象类
对于一些通用的方法或者属性,可以将其定义为接口或者抽象类。这样可以让子类继承并实现这些方法或属性,从而提高代码的灵活性和可扩展性。
实例:
public interface Shape {
void draw();
}
public abstract class ShapeWithDrawMethod {
public abstract void draw();
}
public class Circle extends ShapeWithDrawMethod implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
public class Square extends ShapeWithDrawMethod implements Shape {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}
public class ShapeFactory {
public static Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
} else if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
} else {
throw new IllegalArgumentException("Invalid shape type");
}
}
}
public class Main {
public static void main(String[] args) {
Shape circle = ShapeFactory.getShape("CIRCLE");
if (circle != null) {
circle.draw(); // 调用Circle类中的draw方法,会输出"Drawing a circle"
} else {
System.out.println("Invalid shape type");
}
}
}
在这个示例中,我们定义了一个 Shape 接口和两个实现了该接口的类 Circle 和 Square。同时,我们还定义了一个 ShapeFactory 类,用于根据传入的参数类型返回对应的实例对象。在 Main 类中,我们通过调用 ShapeFactory.getShape() 方法获取一个 Shape 对象,并判断是否为空。如果不为空,则调用其 draw()。
2. 使用私有变量和公有方法
将需要保护的数据放在私有变量中,只提供公有方法供外部调用。这样可以避免外部直接访问和修改私有变量,保证了数据的安全性和封装性。
public class Shape {
private String name;
public Shape(String name) {
this.name = name;
}
public void draw() {
System.out.println("Drawing " + name);
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Shape("Circle");
circle.draw(); // 输出"Drawing Circle"
}
}
在这个示例中,我们定义了一个 Shape 类,其中包含了一个私有变量 name 和一个公有方法 draw()。在构造方法中,我们将传入的参数赋值给私有变量。这样可以保证数据的安全性和封装性。同时,只提供了公有接口,外部无法直接访问私有变量,从而保证了代码的可维护性和安全性。
3. 使用静态方法和常量
对于一些不需要访问实例变量的方法,可以使用静态方法或者常量来实现。这样可以避免在每个实例化的对象中都创建相同的方法或变量,提高了代码的复用性和可维护性。
以下是一个使用静态方法和常量封装的示例:
public class Shape {
private String name;
public Shape(String name) {
this.name = name;
}
public static final String CIRCLE_NAME = "Circle";
public static void draw() {
System.out.println("Drawing " + CIRCLE_NAME);
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Shape("Circle");
Shape.draw(); // 输出"Drawing Circle"
}
}
在这个示例中,我们定义了一个 Shape
类,其中包含了一个私有变量 name
。同时,我们定义了一个静态常量 CIRCLE_NAME
,用于表示圆形的名称。在静态方法 draw()
中,我们直接使用了静态常量 CIRCLE_NAME
,而不需要创建任何实例对象。这样可以避免重复创建对象,提高了代码的效率和可维护性。同时,由于静态方法是属于类本身的,而不是属于类的实例的,因此可以直接通过类名调用,而不需要创建实例对象。
4. 使用注解
Java 中的注解可以用于描述类、方法、字段等元素的行为和属性。通过注解,可以在编译时或者运行时获取元素的信息,从而实现更加灵活和高效的封装。
以下是一个使用注解封装的示例:
public class Shape {
private String name;
public Shape(String name) {
this.name = name;
}
@Override
public String toString() {
return "Shape{name=" + name + "}";
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Shape("Circle");
System.out.println(circle); // 输出"Shape{name=Circle}"
}
}
在这个示例中,我们定义了一个 Shape
类,其中包含了一个私有变量 name
。同时,我们使用了 Java 中的注解 @Override
,用于覆盖父类 Object
中的 toString()
方法。在 toString()
方法中,我们返回了一个字符串,用于表示当前对象的信息。这样可以方便地通过 System.out.println()
方法输出对象的信息,而不需要手动编写打印代码。同时,由于注解是属于类本身的,而不是属于类的实例的,因此可以直接通过类名调用,而不需要创建实例对象。
5.使用枚举类型
对于一些需要限定取值范围的变量,可以使用枚举类型来实现。这样可以避免使用整型等数据类型可能出现的溢出和错误,同时也可以提高代码的可读性和可维护性。
以下是一个使用枚举类型封装的示例:
public enum Shape {
ROUND, CIRCLE;
public static Shape getShapeByName(String shapeName) {
for (Shape shape : values()) {
if (shape.name().equalsIgnoreCase(shapeName)) {
return shape;
}
}
throw new IllegalArgumentException("Invalid shape name: " + shapeName);
}
}
public class Main {
public static void main(String[] args) {
Shape circle = Shape.CIRCLE;
System.out.println(Shape.getShapeByName("Circle")); // 输出"Circle"
}
}
在这个示例中,我们定义了一个 Shape
枚举类型,其中包含了两个枚举值 ROUND
和 CIRCLE
。同时,我们还定义了一个静态方法 getShapeByName()
,用于根据传入的名称参数返回对应的枚举值。在方法中,我们使用了 for-each
循环遍历所有的枚举值,并通过 equalsIgnoreCase()
方法进行比较。如果找到了匹配的枚举值,则直接返回该值。否则,抛出一个 IllegalArgumentException
异常。这样可以保证数据的安全性和封装性,同时提供了更好的类型检查和错误提示。
6. 其他
6.1 多种方式联合封装
1.使用访问控制符:
Java中有public、private和protected等访问控制符,可以限制属性和方法的访问权限。一般来说,属性都应该使用private访问控制符,方法可以根据需要使用public、private或protected。
2.使用getter和setter方法 :
将属性设置为private,然后提供public的getter和setter方法,使得外部可以访问对象的属性,但是不能直接修改它们。同时,可以在getter和setter方法中添加一些控制逻辑,如检查属性的合法性。
3.使用final关键字 :
使用final关键字可以将属性和方法设置为不可变的,这可以避免外部对对象进行不必要的修改,提高代码的安全性和可维护性。
示例:
public class Shape {
private final String name;
public Shape(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
throw new IllegalArgumentException("Cannot modify the shape name");
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Shape("Circle");
System.out.println(circle.getName()); // 输出"Circle"
try {
circle.setName("Square"); // 抛出异常,因为setName方法被final修饰了
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 输出"Cannot modify the shape name"
}
}
}
在这个示例中,我们定义了一个 Shape
类,其中包含了一个私有变量 name
,并且使用了 final 关键字进行修饰。同时,我们使用了访问控制符 public
、private
、get
、set
分别控制了公有、私有、getter、setter 方法的访问权限。这样可以保证数据的安全性和封装性,同时提供了更好的类型检查和错误提示。同时防止了意外修改私有变量的值。由于 final 关键字修饰的方法不能被重新定义或调用,因此可以避免了一些潜在的问题。
6.2 使用包装类
如果需要封装一些原始类型或者复杂类型的数据,可以使用Java提供的包装类,如Integer、Double、List等,通过包装类暴露一些公共接口,隐藏底层实现细节。
使用包装类封装的好处包括:
- 提高代码的可读性和可维护性。包装类可以隐藏内部实现细节,只暴露出必要的接口,使得代码更加清晰易懂。
- 增加代码的灵活性和扩展性。通过在包装类中添加新的功能或修改已有的功能,可以不影响原有的接口和代码逻辑。
- 保护内部实现不被直接访问。将内部实现封装在包装类中,可以防止外部代码直接访问和修改内部状态,从而提高系统的安全性。
- 支持多态性。通过包装类可以将对象的不同实现方式统一起来,从而支持多态性,使得代码更加灵活和易于扩展。
- 可以对数据进行验证和处理。在包装类中可以添加数据验证和处理的方法,从而保证数据的正确性和完整性。
以下是一个使用包装类封装的示例:
public class Shape {
private String name;
public Shape(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
throw new IllegalArgumentException("Cannot modify the shape name");
}
}
public class ShapeWrapper implements Shape {
private final Shape innerShape;
public ShapeWrapper(String name) {
innerShape = new Shape(name);
}
@Override
public String getName() {
return innerShape.getName();
}
@Override
public void setName(String name) throws IllegalArgumentException {
innerShape.setName(name);
}
}
public class Main {
public static void main(String[] args) {
ShapeWrapper circle = new ShapeWrapper("Circle");
System.out.println(circle.getName()); // 输出"Circle"
try {
circle.setName("Square"); // 抛出异常,因为setName方法被final修饰了
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 输出"Cannot modify the shape name"
}
}
}
在这个示例中,我们定义了一个 Shape
类和一个 ShapeWrapper
类。其中,ShapeWrapper
类继承自 Shape
类,并将 Shape
的 get, set 方法重写。降低了耦合,提高了代码的复用性。
总结:
合理的封装可以提高代码的质量和效率,使得代码更加易于维护和扩展,但是切忌不要过度封装嗷!