写优雅代码之聊一聊抽象类2

来源:互联网 发布:紫砂壶开壶 知乎 编辑:程序博客网 时间:2024/06/04 18:08

抽象类在设计模式中最明显的体现就是模板方法模式,除此之外,通过阅读spring的源码,还发现一个有趣的用法

1.抽象类可以实现接口中的部分方法,对于不实现的方法,可以继承抽象下去,例如



在该继承链中, InputStreamSource, Resource是一个接口(Resource接口继承了InputStreamSource,功力大增,实现类可以向上转型嘛), AbstractResource是一个抽象类,该抽象类实现了两个接口中的部分方法,其他未实现的方法继续抽象.

因为AbstractResource含有抽象方法,所以他必须定义为一个抽象类.

抽象类似乎天生就是用来被继承的,不能被实例化.

下面我们来看一下源码,首先来看一下InputStreamSource的源码

/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.core.io;import java.io.IOException;import java.io.InputStream;/** * Simple interface for objects that are sources for an {@link InputStream}. * * <p>This is the base interface for Spring's more extensive {@link Resource} interface. * * <p>For single-use streams, {@link InputStreamResource} can be used for any * given {@code InputStream}. Spring's {@link ByteArrayResource} or any * file-based {@code Resource} implementation can be used as a concrete * instance, allowing one to read the underlying content stream multiple times. * This makes this interface useful as an abstract content source for mail * attachments, for example. * * @author Juergen Hoeller * @since 20.01.2004 * @see java.io.InputStream * @see Resource * @see InputStreamResource * @see ByteArrayResource */public interface InputStreamSource {/** * Return an {@link InputStream} for the content of an underlying resource. * <p>It is expected that each call creates a <i>fresh</i> stream. * <p>This requirement is particularly important when you consider an API such * as JavaMail, which needs to be able to read the stream multiple times when * creating mail attachments. For such a use case, it is <i>required</i> * that each {@code getInputStream()} call returns a fresh stream. * @return the input stream for the underlying resource (must not be {@code null}) * @throws java.io.FileNotFoundException if the underlying resource doesn't exist * @throws IOException if the content stream could not be opened */InputStream getInputStream() throws IOException;}
该接口定义了一个getInputStream()的抽象方法.

接着我们来看一下Resoutce接口

/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.core.io;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.net.URI;import java.net.URL;import java.nio.channels.Channels;import java.nio.channels.ReadableByteChannel;import org.springframework.lang.Nullable;/** * Interface for a resource descriptor that abstracts from the actual * type of underlying resource, such as a file or class path resource. * * <p>An InputStream can be opened for every resource if it exists in * physical form, but a URL or File handle can just be returned for * certain resources. The actual behavior is implementation-specific. * * @author Juergen Hoeller * @since 28.12.2003 * @see #getInputStream() * @see #getURL() * @see #getURI() * @see #getFile() * @see WritableResource * @see ContextResource * @see UrlResource * @see ClassPathResource * @see FileSystemResource * @see PathResource * @see ByteArrayResource * @see InputStreamResource */public interface Resource extends InputStreamSource {/** * Determine whether this resource actually exists in physical form. * <p>This method performs a definitive existence check, whereas the * existence of a {@code Resource} handle only guarantees a valid * descriptor handle. */boolean exists();/** * Indicate whether the contents of this resource can be read via * {@link #getInputStream()}. * <p>Will be {@code true} for typical resource descriptors; * note that actual content reading may still fail when attempted. * However, a value of {@code false} is a definitive indication * that the resource content cannot be read. * @see #getInputStream() */default boolean isReadable() {return true;}/** * Indicate whether this resource represents a handle with an open stream. * If {@code true}, the InputStream cannot be read multiple times, * and must be read and closed to avoid resource leaks. * <p>Will be {@code false} for typical resource descriptors. */default boolean isOpen() {return false;}/** * Determine whether this resource represents a file in a file system. * A value of {@code true} strongly suggests (but does not guarantee) * that a {@link #getFile()} call will succeed. * <p>This is conservatively {@code false} by default. * @since 5.0 * @see #getFile() */default boolean isFile() {return false;}/** * Return a URL handle for this resource. * @throws IOException if the resource cannot be resolved as URL, * i.e. if the resource is not available as descriptor */URL getURL() throws IOException;/** * Return a URI handle for this resource. * @throws IOException if the resource cannot be resolved as URI, * i.e. if the resource is not available as descriptor * @since 2.5 */URI getURI() throws IOException;/** * Return a File handle for this resource. * @throws java.io.FileNotFoundException if the resource cannot be resolved as * absolute file path, i.e. if the resource is not available in a file system * @throws IOException in case of general resolution/reading failures * @see #getInputStream() */File getFile() throws IOException;/** * Return a {@link ReadableByteChannel}. * <p>It is expected that each call creates a <i>fresh</i> channel. * <p>The default implementation returns {@link Channels#newChannel(InputStream)} * with the result of {@link #getInputStream()}. * @return the byte channel for the underlying resource (must not be {@code null}) * @throws java.io.FileNotFoundException if the underlying resource doesn't exist * @throws IOException if the content channel could not be opened * @since 5.0 * @see #getInputStream() */default ReadableByteChannel readableChannel() throws IOException {return Channels.newChannel(getInputStream());}/** * Determine the content length for this resource. * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */long contentLength() throws IOException;/** * Determine the last-modified timestamp for this resource. * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */long lastModified() throws IOException;/** * Create a resource relative to this resource. * @param relativePath the relative path (relative to this resource) * @return the resource handle for the relative resource * @throws IOException if the relative resource cannot be determined */Resource createRelative(String relativePath) throws IOException;/** * Determine a filename for this resource, i.e. typically the last * part of the path: for example, "myfile.txt". * <p>Returns {@code null} if this type of resource does not * have a filename. */@NullableString getFilename();/** * Return a description for this resource, * to be used for error output when working with the resource. * <p>Implementations are also encouraged to return this value * from their {@code toString} method. * @see Object#toString() */String getDescription();}
该接口继承了InputStreamSource接口,定义了
exists
isReadable
isOpen
isFile
getURL
getURI
getFile
readableChannel
contentLength
lastModified
createRelative
getFilename
getDescription

这13个接口,加上继承来的getInputStream这个接口,该类具有14个接口

我么来看一下AbstractResource抽象类

/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.core.io;import java.io.File;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.net.URI;import java.net.URISyntaxException;import java.net.URL;import java.nio.channels.Channels;import java.nio.channels.ReadableByteChannel;import org.springframework.core.NestedIOException;import org.springframework.util.ResourceUtils;/** * Convenience base class for {@link Resource} implementations, * pre-implementing typical behavior. * * <p>The "exists" method will check whether a File or InputStream can * be opened; "isOpen" will always return false; "getURL" and "getFile" * throw an exception; and "toString" will return the description. * * @author Juergen Hoeller * @since 28.12.2003 */public abstract class AbstractResource implements Resource {/** * This implementation checks whether a File can be opened, * falling back to whether an InputStream can be opened. * This will cover both directories and content resources. */@Overridepublic boolean exists() {// Try file existence: can we find the file in the file system?try {return getFile().exists();}catch (IOException ex) {// Fall back to stream existence: can we open the stream?try {InputStream is = getInputStream();is.close();return true;}catch (Throwable isEx) {return false;}}}/** * This implementation always returns {@code true}. */@Overridepublic boolean isReadable() {return true;}/** * This implementation always returns {@code false}. */@Overridepublic boolean isOpen() {return false;}/** * This implementation always returns {@code false}. */@Overridepublic boolean isFile() {return false;}/** * This implementation throws a FileNotFoundException, assuming * that the resource cannot be resolved to a URL. */@Overridepublic URL getURL() throws IOException {throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");}/** * This implementation builds a URI based on the URL returned * by {@link #getURL()}. */@Overridepublic URI getURI() throws IOException {URL url = getURL();try {return ResourceUtils.toURI(url);}catch (URISyntaxException ex) {throw new NestedIOException("Invalid URI [" + url + "]", ex);}}/** * This implementation throws a FileNotFoundException, assuming * that the resource cannot be resolved to an absolute file path. */@Overridepublic File getFile() throws IOException {throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");}/** * This implementation returns {@link Channels#newChannel(InputStream)} * with the result of {@link #getInputStream()}. * <p>This is the same as in {@link Resource}'s corresponding default method * but mirrored here for efficient JVM-level dispatching in a class hierarchy. */@Overridepublic ReadableByteChannel readableChannel() throws IOException {return Channels.newChannel(getInputStream());}/** * This implementation reads the entire InputStream to calculate the * content length. Subclasses will almost always be able to provide * a more optimal version of this, e.g. checking a File length. * @see #getInputStream() */@Overridepublic long contentLength() throws IOException {InputStream is = getInputStream();try {long size = 0;byte[] buf = new byte[255];int read;while ((read = is.read(buf)) != -1) {size += read;}return size;}finally {try {is.close();}catch (IOException ex) {}}}/** * This implementation checks the timestamp of the underlying File, * if available. * @see #getFileForLastModifiedCheck() */@Overridepublic long lastModified() throws IOException {long lastModified = getFileForLastModifiedCheck().lastModified();if (lastModified == 0L) {throw new FileNotFoundException(getDescription() +" cannot be resolved in the file system for resolving its last-modified timestamp");}return lastModified;}/** * Determine the File to use for timestamp checking. * <p>The default implementation delegates to {@link #getFile()}. * @return the File to use for timestamp checking (never {@code null}) * @throws FileNotFoundException if the resource cannot be resolved as * an absolute file path, i.e. is not available in a file system * @throws IOException in case of general resolution/reading failures */protected File getFileForLastModifiedCheck() throws IOException {return getFile();}/** * This implementation throws a FileNotFoundException, assuming * that relative resources cannot be created for this resource. */@Overridepublic Resource createRelative(String relativePath) throws IOException {throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());}/** * This implementation always returns {@code null}, * assuming that this resource type does not have a filename. */@Overridepublic String getFilename() {return null;}/** * This implementation returns the description of this resource. * @see #getDescription() */@Overridepublic String toString() {return getDescription();}/** * This implementation compares description strings. * @see #getDescription() */@Overridepublic boolean equals(Object obj) {return (obj == this ||(obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));}/** * This implementation returns the description's hash code. * @see #getDescription() */@Overridepublic int hashCode() {return getDescription().hashCode();}}
该抽象类实现了Resource接口,咋一看,似乎Resource中的抽象方法他都实现了,我们在仔细看一下,似乎InputStream类中的getInputStream()方法没有被实现,而是被作为抽象方法调用了,这是个钩子,调用子类的一个钩子,我们看一下源码

@Overridepublic boolean exists() {// Try file existence: can we find the file in the file system?try {return getFile().exists();}catch (IOException ex) {// Fall back to stream existence: can we open the stream?try {InputStream is = getInputStream();is.close();return true;}catch (Throwable isEx) {return false;}}}
在exists()这个方法中,getInputStream()方法被作为一个钩子使用,其他方法中也被这样用了.这就是我们开篇所讲的抽象类可以实现接口中的部分方法,对于不实现的方法,可以继承抽象下去.另外,我们还可以验证一下,如何验证呢?很简单,我们只要把该类的abstract关键字移除,看是否报错.如果没报错,说明该抽象类实现了接口中的所有方法,反之则没有完全实现.

接下来我们在来看一下钩子的用法,以它一个实现类ClassPathResource为例来说明,继承体系如下


我们来看一下ClassPathResource类的源码

/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.core.io;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.net.URL;import org.springframework.lang.Nullable;import org.springframework.util.Assert;import org.springframework.util.ClassUtils;import org.springframework.util.ObjectUtils;import org.springframework.util.StringUtils;/** * {@link Resource} implementation for class path resources. Uses either a * given {@link ClassLoader} or a given {@link Class} for loading resources. * * <p>Supports resolution as {@code java.io.File} if the class path * resource resides in the file system, but not for resources in a JAR. * Always supports resolution as URL. * * @author Juergen Hoeller * @author Sam Brannen * @since 28.12.2003 * @see ClassLoader#getResourceAsStream(String) * @see Class#getResourceAsStream(String) */public class ClassPathResource extends AbstractFileResolvingResource {private final String path;@Nullableprivate ClassLoader classLoader;@Nullableprivate Class<?> clazz;/** * Create a new {@code ClassPathResource} for {@code ClassLoader} usage. * A leading slash will be removed, as the ClassLoader resource access * methods will not accept it. * <p>The thread context class loader will be used for * loading the resource. * @param path the absolute path within the class path * @see java.lang.ClassLoader#getResourceAsStream(String) * @see org.springframework.util.ClassUtils#getDefaultClassLoader() */public ClassPathResource(String path) {this(path, (ClassLoader) null);}/** * Create a new {@code ClassPathResource} for {@code ClassLoader} usage. * A leading slash will be removed, as the ClassLoader resource access * methods will not accept it. * @param path the absolute path within the classpath * @param classLoader the class loader to load the resource with, * or {@code null} for the thread context class loader * @see ClassLoader#getResourceAsStream(String) */public ClassPathResource(String path, @Nullable ClassLoader classLoader) {Assert.notNull(path, "Path must not be null");String pathToUse = StringUtils.cleanPath(path);if (pathToUse.startsWith("/")) {pathToUse = pathToUse.substring(1);}this.path = pathToUse;this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());}/** * Create a new {@code ClassPathResource} for {@code Class} usage. * The path can be relative to the given class, or absolute within * the classpath via a leading slash. * @param path relative or absolute path within the class path * @param clazz the class to load resources with * @see java.lang.Class#getResourceAsStream */public ClassPathResource(String path, @Nullable Class<?> clazz) {Assert.notNull(path, "Path must not be null");this.path = StringUtils.cleanPath(path);this.clazz = clazz;}/** * Create a new {@code ClassPathResource} with optional {@code ClassLoader} * and {@code Class}. Only for internal usage. * @param path relative or absolute path within the classpath * @param classLoader the class loader to load the resource with, if any * @param clazz the class to load resources with, if any */protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz) {this.path = StringUtils.cleanPath(path);this.classLoader = classLoader;this.clazz = clazz;}/** * Return the path for this resource (as resource path within the class path). */public final String getPath() {return this.path;}/** * Return the ClassLoader that this resource will be obtained from. */@Nullablepublic final ClassLoader getClassLoader() {return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);}/** * This implementation checks for the resolution of a resource URL. * @see java.lang.ClassLoader#getResource(String) * @see java.lang.Class#getResource(String) */@Overridepublic boolean exists() {return (resolveURL() != null);}/** * Resolves a URL for the underlying class path resource. * @return the resolved URL, or {@code null} if not resolvable */@Nullableprotected URL resolveURL() {if (this.clazz != null) {return this.clazz.getResource(this.path);}else if (this.classLoader != null) {return this.classLoader.getResource(this.path);}else {return ClassLoader.getSystemResource(this.path);}}/** * This implementation opens an InputStream for the given class path resource. * @see java.lang.ClassLoader#getResourceAsStream(String) * @see java.lang.Class#getResourceAsStream(String) */@Overridepublic InputStream getInputStream() throws IOException {InputStream is;if (this.clazz != null) {is = this.clazz.getResourceAsStream(this.path);}else if (this.classLoader != null) {is = this.classLoader.getResourceAsStream(this.path);}else {is = ClassLoader.getSystemResourceAsStream(this.path);}if (is == null) {throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");}return is;}/** * This implementation returns a URL for the underlying class path resource, * if available. * @see java.lang.ClassLoader#getResource(String) * @see java.lang.Class#getResource(String) */@Overridepublic URL getURL() throws IOException {URL url = resolveURL();if (url == null) {throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");}return url;}/** * This implementation creates a ClassPathResource, applying the given path * relative to the path of the underlying resource of this descriptor. * @see org.springframework.util.StringUtils#applyRelativePath(String, String) */@Overridepublic Resource createRelative(String relativePath) {String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);return new ClassPathResource(pathToUse, this.classLoader, this.clazz);}/** * This implementation returns the name of the file that this class path * resource refers to. * @see org.springframework.util.StringUtils#getFilename(String) */@Overridepublic String getFilename() {return StringUtils.getFilename(this.path);}/** * This implementation returns a description that includes the class path location. */@Overridepublic String getDescription() {StringBuilder builder = new StringBuilder("class path resource [");String pathToUse = path;if (this.clazz != null && !pathToUse.startsWith("/")) {builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));builder.append('/');}if (pathToUse.startsWith("/")) {pathToUse = pathToUse.substring(1);}builder.append(pathToUse);builder.append(']');return builder.toString();}/** * This implementation compares the underlying class path locations. */@Overridepublic boolean equals(Object obj) {if (obj == this) {return true;}if (obj instanceof ClassPathResource) {ClassPathResource otherRes = (ClassPathResource) obj;return (this.path.equals(otherRes.path) &&ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));}return false;}/** * This implementation returns the hash code of the underlying * class path location. */@Overridepublic int hashCode() {return this.path.hashCode();}}
在第165行,我们明显看到了getInputStream的具体实现,利用钩子,我们很容易就调用到了它.

总结:从这个案例中,我们可以至少学到以下知识

1.可以把抽象方法作为钩子使用,调用到继承子类中的实现方法.这也就是多肽的一个用法

2.抽象类可以实现接口中的部分方法,对于未实现的方法,可以继续抽象下去

3.面向oo设计的一个原则,封装变化,面向抽象或者说面向接口编程.这样可以降低耦合度,提高内聚性.










原创粉丝点击