打破一个类,或者强加一个接口来限制访问?
当不同类需要以不同方式从外部访问类的功能时,对类进行分区的最佳方法是什么?希望下面的示例能让问题变得清楚:)
我有一个 Java 类,它访问目录中的单个位置,允许外部类对其执行读/写操作。读取操作返回目录的使用情况统计信息(例如可用磁盘空间、写入次数等);显然,写操作允许外部类将数据写入磁盘。这些方法始终在同一位置工作,并从外部源(传递给构造函数)接收其配置(例如使用哪个目录、最小磁盘空间等)。
这个类看起来像这样:
public class DiskHandler {
public DiskHandler(String dir, int minSpace) {
...
}
public void writeToDisk(String contents, String filename) {
int space = getAvailableSpace();
...
}
public void getAvailableSpace() {
...
}
}
还有很多事情要做,但这就足够了。
这个类需要由两个外部类以不同的方式访问。一个类需要访问读操作;另一个需要访问读取和写入操作。
public class DiskWriter {
DiskHandler diskHandler;
public DiskWriter() {
diskHandler = new DiskHandler(...);
}
public void doSomething() {
diskHandler.writeToDisk(...);
}
}
public class DiskReader {
DiskHandler diskHandler;
public DiskReader() {
diskHandler = new DiskHandler(...);
}
public void doSomething() {
int space = diskHandler.getAvailableSpace(...);
}
}
此时,两个类共享同一个类,但只应读取的类可以访问写入方法。
解决方案 1
我可以将这个类分成两部分。一个类将处理读取操作,另一个类将处理写入操作:
// NEW "UTILITY" CLASSES
public class WriterUtil {
private ReaderUtil diskReader;
public WriterUtil(String dir, int minSpace) {
...
diskReader = new ReaderUtil(dir, minSpace);
}
public void writeToDisk(String contents, String filename) {
int = diskReader.getAvailableSpace();
...
}
}
public class ReaderUtil {
public ReaderUtil(String dir, int minSpace) {
...
}
public void getAvailableSpace() {
...
}
}
// MODIFIED EXTERNALLY-ACCESSING CLASSES
public class DiskWriter {
WriterUtil diskWriter;
public DiskWriter() {
diskWriter = new WriterUtil(...);
}
public void doSomething() {
diskWriter.writeToDisk(...);
}
}
public class DiskReader {
ReaderUtil diskReader;
public DiskReader() {
diskReader = new ReaderUtil(...);
}
public void doSomething() {
int space = diskReader.getAvailableSpace(...);
}
}
此解决方案可防止类访问它们不应该访问的方法,但它也会破坏封装。原始的 DiskHandler 类是完全独立的,只需要通过单个构造函数配置参数。通过将功能分解为读/写类,它们都与目录有关,并且都需要使用各自的值进行实例化。本质上,我真的不想重复这些担忧。
解决方案 2
我可以实现一个仅提供读取操作的接口,并在类仅需要访问这些方法时使用它。
界面可能看起来像这样:
public interface Readable {
int getAvailableSpace();
}
Reader 类将像这样实例化对象:
Readable diskReader;
public DiskReader() {
diskReader = new DiskHandler(...);
}
这个解决方案似乎很脆弱,并且将来容易造成混乱。它不能保证开发人员将来会使用正确的接口。对 DiskHandler 实现的任何更改也可能需要更新接口以及访问类。我比以前的解决方案更喜欢它,但也不是很多。
坦率地说,这两种解决方案似乎都不完美,但我不确定是否应该优先选择其中一种。我真的不想破坏原来的类,但我也不知道从长远来看这个界面是否能给我带来很多好处。
我还缺少其他解决方案吗?
What's the best way of partitioning a class when its functionality needs to be externally accessed in different ways by different classes? Hopefully the following example will make the question clear :)
I have a Java class which accesses a single location in a directory allowing external classes to perform read/write operations to it. Read operations return usage stats on the directory (e.g. available disk space, number of writes, etc.); write operations, obviously, allow external classes to write data to the disk. These methods always work on the same location, and receive their configuration (e.g. which directory to use, min disk space, etc.) from an external source (passed to the constructor).
This class looks something like this:
public class DiskHandler {
public DiskHandler(String dir, int minSpace) {
...
}
public void writeToDisk(String contents, String filename) {
int space = getAvailableSpace();
...
}
public void getAvailableSpace() {
...
}
}
There's quite a bit more going on, but this will do to suffice.
This class needs to be accessed differently by two external classes. One class needs access to the read operations; the other needs access to both read and write operations.
public class DiskWriter {
DiskHandler diskHandler;
public DiskWriter() {
diskHandler = new DiskHandler(...);
}
public void doSomething() {
diskHandler.writeToDisk(...);
}
}
public class DiskReader {
DiskHandler diskHandler;
public DiskReader() {
diskHandler = new DiskHandler(...);
}
public void doSomething() {
int space = diskHandler.getAvailableSpace(...);
}
}
At this point, both classes share the same class, but the class which should only read has access to the write methods.
Solution 1
I could break this class into two. One class would handle read operations, and the other would handle writes:
// NEW "UTILITY" CLASSES
public class WriterUtil {
private ReaderUtil diskReader;
public WriterUtil(String dir, int minSpace) {
...
diskReader = new ReaderUtil(dir, minSpace);
}
public void writeToDisk(String contents, String filename) {
int = diskReader.getAvailableSpace();
...
}
}
public class ReaderUtil {
public ReaderUtil(String dir, int minSpace) {
...
}
public void getAvailableSpace() {
...
}
}
// MODIFIED EXTERNALLY-ACCESSING CLASSES
public class DiskWriter {
WriterUtil diskWriter;
public DiskWriter() {
diskWriter = new WriterUtil(...);
}
public void doSomething() {
diskWriter.writeToDisk(...);
}
}
public class DiskReader {
ReaderUtil diskReader;
public DiskReader() {
diskReader = new ReaderUtil(...);
}
public void doSomething() {
int space = diskReader.getAvailableSpace(...);
}
}
This solution prevents classes from having access to methods they should not, but it also breaks encapsulation. The original DiskHandler class was completely self-contained and only needed config parameters via a single constructor. By breaking apart the functionality into read/write classes, they both are concerned with the directory and both need to be instantiated with their respective values. In essence, I don't really care to duplicate the concerns.
Solution 2
I could implement an interface which only provisions read operations, and use this when a class only needs access to those methods.
The interface might look something like this:
public interface Readable {
int getAvailableSpace();
}
The Reader class would instantiate the object like this:
Readable diskReader;
public DiskReader() {
diskReader = new DiskHandler(...);
}
This solution seems brittle, and prone to confusion in the future. It doesn't guarantee developers will use the correct interface in the future. Any changes to the implementation of the DiskHandler could also need to update the interface as well as the accessing classes. I like it better than the previous solution, but not by much.
Frankly, neither of these solutions seems perfect, but I'm not sure if one should be preferred over the other. I really don't want to break the original class up, but I also don't know if the interface buys me much in the long run.
Are there other solutions I'm missing?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我会选择界面,结合一点依赖注入 - 你不需要直接在您的读取器或写入器类中实例化一个
new DiskHandler
,它们在其构造函数中接受适当类型的对象。因此,您的
DiskReader
将接受Readable
,而您的DiskWriter
将获得ReadWrite
(或DiskHandler
直接,如果你不想为读写模式创建一个接口,尽管我建议否则 - 通过interface ReadWrite extends Readable
或类似的)。如果您始终使用适当的接口注入它,则不必担心使用不正确。I'd go with the interface, combined with a little bit of Dependency Injection - you don't instantiate a
new DiskHandler
directly inside your reader or writer classes, they accept an object of the appropriate type in their constructors.So your
DiskReader
would accept aReadable
, and yourDiskWriter
would get aReadWrite
(or aDiskHandler
directly, if you don't want to make an interface for the read-write mode, although I'd suggest otherwise - viainterface ReadWrite extends Readable
or similar). If you consistently inject it using the appropriate interface, you won't have to worry about incorrect usage.我认为接口也是这里最面向对象的方法。第一种方法基本上将语义相关方法的集合重构为一堆小实用函数:这不是您想要的。
第二种解决方案允许您班级的用户准确表达他们使用它的原因。就像好的 Java 代码通常声明
List
、Set
和NavigableMap
而不是ArrayList
、一样。 >HashSet
和TreeMap
,您的类的用户可以将变量声明为仅Readable
或Writeable
,而不是声明对任何具体子类的依赖。显然,有人在某些时候仍然需要调用 new,但正如 tzaman 指出的,这可以通过 setter 和依赖注入来处理。如果您在运行时需要未知数量的工厂,请改为注入工厂。
我很好奇:为什么您认为对 DiskHandler 实现的任何更改都会导致使用 Reader 的类发生更改?如果 Reader 可以被定义为一个稳定的接口,那么该接口应该清楚地阐明其语义契约(在 Javadoc 中)。如果用户针对该接口进行编码,则可以在他们不知情的情况下在幕后更改实现。当然,如果界面本身发生变化,它们也必须改变,但这与第一个解决方案有什么不同?
还有一件事需要考虑:假设您有多个线程,其中大多数需要一个 Reader,但其中一些需要一个 Writer,所有线程都指向同一个文件。您可以让
DiskHandler
实现Reader
和Writer
并将单个实例注入到所有线程。通过在需要的地方设置适当的 ReadWriteLocks 和 Synchronizeds ,可以在该对象内部处理并发性。在您的第一个解决方案中这怎么可能?I think the interface is also the most object-oriented approach here. The first approach basically refactors your collection of semantically-related methods into a bunch of little utility functions: not what you want.
The second solution allows users of your class to express exactly why they are using it. In the same way that good Java code typically declares
List
,Set
, andNavigableMap
rather thanArrayList
,HashSet
, andTreeMap
, users of your class can declare a variable to be only aReadable
orWriteable
, rather than declaring a dependency on any concrete subclass.Obviously, someone still needs to call
new
at some point, but as tzaman pointed out, this can be handled with setters and dependency injection. If you need an unknown number of them at runtime, inject a factory instead.I am curious: why do you think that any changes to the implementation of
DiskHandler
would result in changes to the classes that useReader
? IfReader
can be defined to be a stable interface, the interface should clearly spell out its semantic contract (in the Javadoc). If users code against that interface, the implementation can be changed behind the scenes without their knowledge. Sure, if the interface itself changes they have to change, but how is that different from the first solution?One more thing to think about: Let's say you have multiple threads, most of which need a
Reader
, but some of which need aWriter
, all to the same file. You could haveDiskHandler
implement bothReader
andWriter
and inject a single instance to all threads. Concurrency could be handled internally to this object by having appropriateReadWriteLocks
andsynchronizeds
where they need to go. How would this be possible in your first solution?