内容简介:实现这样一个类中使用在下面的程序中,我们期望
- 在包的内部是用继承,不存在跨包继承。
- 专门为了扩展而设计,并且具备很好的文档说明。
一个例子
实现这样一个 HashSet
,可以跟踪从它被创建之后曾经添加过几个元素。
使用继承实现
public class InstrumentedSet<E> extends HashSet<E> { // The number of attempted element insertions private int addCount = 0; public InstrumentedSet() { } public InstrumentedSet(int initCap, float loadFactor) { super(initCap, loadFactor); } @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c) { addCount += c.size(); return super.addAll(c); } public int getAddCount() { return addCount; } } 复制代码
类中使用 addCount
字段记录添加元素的次数,并覆盖父类的 add()
和 addAll()
实现,对 addCount
字段进行设值。
在下面的程序中,我们期望 getAddCount()
返回3,但实际上返回的是6。
InstrumentedSet<String> s = new InstrumentedSet<String>(); s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); 复制代码
问题出在于:在 HashSet
中, addAll()
的实现是基于 add()
方法的。子类在扩展父类的功能时,如果不清楚实现细节,是非常危险的,况且父类的实现在未来可能是变化的,毕竟它并不是为扩展而设计的。
使用组合实现
不用扩展现有的类,而是在新的类中增加一个私有字段,引用现有类的实例。这种设计被叫做 组合 。
先创建一个干净的 SetWrapper
组合类。
public class SetWrapper<E> implements Set<E> { private final Set<E> s; public SetWrapper(Set<E> s) { this.s = s; } public void clear() { s.clear(); } public boolean contains(Object o) { return s.contains(o); } public boolean isEmpty() { return s.isEmpty(); } public int size() { return s.size(); } public Iterator<E> iterator() { return s.iterator(); } public boolean add(E e) { return s.add(e); } public boolean remove(Object o){ return s.remove(o); } public boolean containsAll(Collection<?> c) { return s.containsAll(c); } public boolean addAll(Collection<? extends E> c) { return s.addAll(c); } public boolean removeAll(Collection<?> c) { return s.removeAll(c); } public boolean retainAll(Collection<?> c) { return s.retainAll(c); } public Object[] toArray() { return s.toArray(); } public <T> T[] toArray(T[] a) { return s.toArray(a); } @Override public boolean equals(Object o) { return s.equals(o); } @Override public int hashCode() { return s.hashCode(); } @Override public String toString() { return s.toString(); } } 复制代码
SetWrapper
实现了装饰模式,通过引用 Set<E>
类型的字段,面向接口编程,相比直接继承 HashSet
类来得更灵活。可以在调用该类的构造方法中传入任意 Set
具体类。扩展该类以实现需求。
public class InstrumentedSet<E> extends SetWrapper<E> { private int addCount = 0; public InstrumentedSet(Set<E> s) { super(s); } @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c) { addCount += c.size(); return super.addAll(c); } public int getAddCount() { return addCount; } } 复制代码
举一反三
注:以下代码均是伪代码,组合方式的实现待封装成 Android 库并开源,敬请期待。没错,这里打了个广告。
笔者曾开发的某个应用有以下2张截图:
详情页面和评论列表页面均复用了评论项的实现。
评论列表页面的 GameComentsAdapter
。
public class GameCommentsAdapter extends RecyclerView.Adapter<BaseViewHolder> { private static final int ITEM_TYPE_COMMENT = 1; private List<Object> mDataSet; @Override public int getItemViewType(int position) { Object item = getItem(position); if (item instanceof Comment) { return ITEM_TYPE_COMMENT; } return super.getItemViewType(position); } protected Object getItem(int position) { return mDataSet.get(position); } @NonNull @Override public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == ITEM_TYPE_COMMENT) { View itemView = inflater.inflate(R.layout.item_comment, parent, false); return new CommentViewHolder(itemView); } return null; } @Override public int getItemCount() { return mDataSet.size(); } } 复制代码
if-else 方式实现
修改 GameComentsAdapter
类,增加对游戏详情项的适配支持。
public class GameCommentsAdapter extends RecyclerView.Adapter<BaseViewHolder> { private static final int ITEM_TYPE_COMMENT = 1; private static final int ITEM_TYPE_GAME_DETAIL = 2; private List<Object> mDataSet; @Override public int getItemViewType(int position) { Object item = getItem(position); if (item instanceof Comment) { return ITEM_TYPE_COMMENT; } if (item instanceof GameDetail) { return ITEM_TYPE_GAME_DETAIL; } return super.getItemViewType(position); } protected Object getItem(int position) { return mDataSet.get(position); } @NonNull @Override public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == ITEM_TYPE_COMMENT) { View itemView = inflater.inflate(R.layout.item_comment, parent, false); return new CommentViewHolder(itemView); } if (viewType == ITEM_TYPE_GAME_DETAIL) { View itemView = inflater.inflate(R.layout.item_game_detail, parent, false); return new GameDetailViewHolder(itemView); } return null; } @Override public int getItemCount() { return mDataSet.size(); } } 复制代码
在游戏详情页面为 RecyclerView
创建一个 GameCommentsAdapter
对象。但该方式会让 GameCommentsAdapter
变得臃肿,也不满足OCP开闭原则。
继承方式实现
扩展一个 Adapter
至少要实现 getItemViewType()
、 onCreateViewHolder()
等方法,为了复用 GameComentsAdapter
类中对评论项,详情页面的 GameDetailAdapter
继承该类。
class GameDetailAdapter extends GameCommentsAdapter { private static final int ITEM_TYPE_GAME_DETAIL = 2; @Override public int getItemViewType(int position) { Object item = getItem(position); if (item instanceof GameDetail) { return ITEM_TYPE_GAME_DETAIL; } return super.getItemViewType(position); } @NonNull @Override public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { if (viewType == ITEM_TYPE_GAME_DETAIL) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View itemView = inflater.inflate(R.layout.item_game_detail, parent, false); return new GameDetailViewHolder(itemView); } return super.onCreateViewHolder(parent, viewType); } } 复制代码
突然来了一个新需求
产品希望在详情页面添加推荐项,复用首页列表项,如下图所示:
实现效果如下图所示:
Java 是单继承的, GameDetailAdapter
已经继承了 GameComentsAdapter
类了,无法再继承 HomeAdapter
。
难道继续在 GameComentsAdapter
类中增加 if
判断?
组合方式
先把各个列表项的创建、绑定等抽离出来,引入 Subadapter
类。
interface Subadapter { boolean isFor(Object item); int getLayoutId(); BaseViewHolder onCreateViewHolder(View itemView); } 复制代码
实现 CommentSubadapter
。
class CommentSubadapter implements Subadapter { @Override public boolean isFor(Object item) { return item instanceof Comment; } @Override public int getLayoutId() { return R.layout.item_comment; } @Override public BaseViewHolder onCreateViewHolder(View itemView) { return new CommentViewHolder(itemView); } } 复制代码
实现 GameDetailSubadapter
。
class GameDetailSubadapter implements Subadapter { @Override public boolean isFor(Object item) { return item instanceof GameDetail; } @Override public int getLayoutId() { return R.layout.item_game_detail; } @Override public BaseViewHolder onCreateViewHolder(View itemView) { return new GameDetailViewHolder(itemView); } } 复制代码
实现 SubadapterManager
。
class SubadapterManager { private SparseArray<Subadapter> mSubadapters = new SparseArray<>(); public void addSubadapter(int itemType, Subadapter subadapter) { mSubadapters.put(itemType, subadapter); } public int getItemViewType(Object item) { for (int i = 0; i < mSubadapters.size(); i++) { Subadapter subadapter = mSubadapters.get(i); if (subadapter.isFor(item)) { return mSubadapters.keyAt(i); } } return 0; } public BaseViewHolder onCreateViewHolder(ViewGroup parent, int type) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); Subadapter subadapter = mSubadapters.get(type); View itemView = inflater.inflate(subadapter.getLayoutId(), parent, false); return subadapter.onCreateViewHolder(itemView); } } 复制代码
实现唯一的 Adapter
。
class Adapter extends RecyclerView.Adapter<BaseViewHolder> { private List<Object> mDataSet; private SubadapterManager mSubadapterManager; protected Object getItem(int position) { return mDataSet.get(position); } public void addSubadapter(int itemType, Subadapter subadapter) { mSubadapterManager.addSubadapter(itemType, subadapter); } @Override public int getItemViewType(int position) { Object item = getItem(position); return mSubadapterManager.getItemViewType(item); } @NonNull @Override public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return mSubadapterManager.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) { holder.bind(getItem(position)); } @Override public int getItemCount() { return mDataSet.size(); } public void setDataSet(List<Object> set) { mDataSet = set; } } 复制代码
组合起来使用。在评论列表页面,创建一个 Adapter
实例,并添加评论项功能。
List<Object> dataSet = new ArrayList<>(); dataSet.add(new Comment()); dataSet.add(new GameDetail()); Adapter adapter = new Adapter(); adapter.addSubadapter(ITEM_TYPE_COMMENT, new CommentSubadapter()); adapter.addSubadapter(ITEM_TYPE_GAME_DETAIL, new GameDetailSubadapter()); adapter.setDataSet(dataSet); 复制代码
实现 GameSubadapter
完成新需求。
class GameSubadapter implements Subadapter { @Override public boolean isFor(Object item) { return item instanceof Game; } @Override public int getLayoutId() { return R.layout.item_game; } @Override public BaseViewHolder onCreateViewHolder(View itemView) { return new GameViewHolder(itemView); } } 复制代码
在游戏详情页面,创建一个 Adapter
实例,并添加游戏项功能。
List<Object> dataSet = new ArrayList<>(); dataSet.add(new Comment()); dataSet.add(new GameDetail()); dataSet.add(new Game()); Adapter adapter = new Adapter(); adapter.addSubadapter(ITEM_TYPE_COMMENT, new CommentSubadapter()); adapter.addSubadapter(ITEM_TYPE_GAME_DETAIL, new GameDetailSubadapter()); adapter.addSubadapter(ITEM_TYPE_GAME, new GameSubadapter()); adapter.setDataSet(dataSet); 复制代码
当某个页面不再支持评论项时,我们只要删除以下代码即可,不会修改到其他地方,满足OCP设计原则。
dataSet.add(new Comment()); adapter.addSubadapter(ITEM_TYPE_COMMENT, new CommentSubadapter()); 复制代码
延伸
在 Java 生态圈之外,有不少组合优于继承的实践。
Kotlin
Kotlin 语言有 delegation 机制,可以方便开发者使用组合。
interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() } 复制代码
Kotlin 版 InstrumentedHashSet
class InstrumentedHashSet<E>(val set: MutableSet<E>) : MutableSet<E> by set { private var addCount : Int = 0 override fun add(element: E): Boolean { addCount++ return set.add(element) } override fun addAll(elements: Collection<E>): Boolean { addCount += elements.size return set.addAll(elements) } } 复制代码
Go
Go 语言没有继承机制,通过原生支持组合来实现代码的复用。以下分别是 Reader
和 Writer
接口定义。
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } 复制代码
通过组合可以定义出具备读取和写入的新类型。
type ReadWriter interface { Reader Writer } 复制代码
上述的例子是接口组合,也可以是实现组合。 (下面的例子来自 Go in Action 一书)
type user struct { name string email string } // notify implements a method that can be called via // a value of type user. func (u *user) notify() { fmt.Printf("Sending user email to %s<%s>\n", u.name, u.email) } // admin represents an admin user with privileges. type admin struct { user // Embedded Type level string } // main is the entry point for the application. func main() { // Create an admin user. ad := admin{ user: user{ name: "john smith", email: "john@yahoo.com", }, level: "super", } // We can access the inner type's method directly. ad.user.notify() // The inner type's method is promoted. ad.notify() } 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 欺骗技术优于蜜罐的8个理由
- 哈希图:它会优于区块链吗?
- android – RxJava2优于AsyncTask
- 随机森林算法预测法官判决,准确度优于人类水平
- Swift 中的面向协议编程:是否优于面向对象编程?
- V神再谈以太坊2.0:展望未来5到10年发展路线,以太坊将优于比特币
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。