前言
时间如梭,少年仍在奔跑!!!
Ⅰ.里氏替换原则
简述:关于里氏替换原则,可能在每天的代码中都有出现关于这一原则的使用,只是一直都在使用,而没有意识到这就是所谓的里氏替换原则。里氏替换原则的思想是:”基类可实现的功能,子类也可以实现”,
下面代码,假设有一个List参数的方法C,里面的逻辑是根据索引找到相应的集合元素,那么当需要list的实现类的索引查找功能时,可以将list的实现类(ArrayList、LinkedList等)任意一个作为方法C的参数传入,类似这样的代码可能每天都会遇到,可这就是里氏替换原则的体现;
public String C(List<String> contents,int index){
return contents.get(index);
}
下面代码,接着假设有一个方法D声明的返回类型是List,可下面代码真实返回的类型却是ArrayList,而调用者并不知道返回的类型是ArrayList,只知道给其返回了List,这也是里氏替换原则的体现;
public List<Integer> D(){
//...省略
//ArrayList<Integer> ids = new ArrayList<Integer>;
return id;
}
上面列举的两个例子都是集合中接口和实现类之间的关系,也就是基类与子类的关系。基类可以实现的功能,子类可以代替基类实现相应的功能,这也是java面向对象特性的体现,所以在java语言里,更实在的体现出了里氏替换原则。其实在开发中的代码,里氏替换原则应该是随处可见的,再看看下面的代码,是不是跟上面都同样的体现;
//主函数
class App{
public static void main(String args[]){
ArrayList<String> str = getHappyNeyYear();
NewYearFactory newYearFactory = new NewNewYearFactoryImpl();
List<Integer> year = newYearFactory.createNewYear(str);
}
}
//演示接口
interface NewYearFactory{
List<Integer> createNewYear(List<String> strContent);
}
//实现类
class NewYearFactoryImpl implements NewYearFactory{
@Override
public List<Integer> createNewYear(List<String> strContent) {
ArrayList<Integer> year = api.getYear(strContent);
return year;
}
}
总结:基类可实现的功能,子类同样可以实现,在继承或实现中,子类延续了基类的特性。
Ⅱ.接口隔离原则
简述:接口隔离原则的核心思想是要求接口不要过于通用,须追求专一的功能.
假设下面Hobit接口是提供给开发者选择爱好的,而刚好A同学的爱好是逛街,那么A同学是不是就要重写Hobit接口,接着实现shoppting函数的逻辑代码.
public interface Hobit{
void coding();
void readBook();
void shopping();
}
public static void doHobit(){
Hobit hobit = new Hobit() {
@Override
public void coding() {
}
@Override
public void readBook() {
}
@Override
public void shopping() {
//...
}
};
}
上面假设A同学的爱好是逛街,可在实现Hobit接口的时候却也得实现coding和readBook这两个方法,这不是多余吗?那么可以去掉这两个方法吗? 假如去掉这两个方法,这时候刚好B同学的爱好是读书,那么Hobit接口没有了readBook这个方法,又该怎么办呢?
上面的假设会不会觉得貌似很矛盾,接口太通用,会觉得出现多余的代码;接口太专一,那多出来的功能又该怎么实现呢?如果你熟悉Android关于设置控件(比如TextView、Button等)的点击事件/长按事件/触摸事件,或许你会恍然大悟,看看下面的代码,怎么实现设置控件的点击事件
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
button.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
点击事件由OnClickListener接口负责,长按事件由OnLongClickListener接口负责,触摸事件由OnTouchListener接口负责,开发者要处理什么事件,只需要实现相应的接口。这样的话不是可以解决上面出现的两个问题
- 接口通用导致代码多余的问题;
- 接口专一导致功能欠缺的问题.
接着再看看下面关于Android属性动画监听的接口实现,是不是觉得Android系统关于实现动画的监听怎么违背了接口隔离原则,实现动画监听接口,还得重写里面的四个方法,太多余了吧!其实可能Android系统开发者当初在设计这个接口的时候,考虑到开发者可能需要同时使用到其中几个方法,所以一并提供了.当然,这也有好处,也是有坏处的,为了完善这个接口,Android系统开发者也重新提供了另一接口AnimatorListenerAdapter,在该接口中已对AnimatorListener的方法进行了空实现,开发者只需要重写所需的方法即可.
Button button = new Button(this);
ObjectAnimator animator = ObjectAnimator.ofFloat(button, "rotationX", 0, 360);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
总结:在开发中,应追求专一接口,这样可以避免很多的问题;那么假设你在开发项目的第一版本时,写了上面的Hobit接口,当某天由于需求变更而得去增加爱好选项,那么这时是否将增加的爱好选项添加到Hobit接口里,这样是不是会导致项目之前所有实现Hobit接口的地方都得进行更改,这样就麻烦~~,所以专一接口是能避免很多问题的
附加:上面Hobit接口关于爱好的,或许列举得不是很好,但大致了解就好.
Ⅲ.依赖倒置原则
在传统软件的开发中,通常是接口或抽象类依赖于具体类,当具体类有变动,那么就得改变其接口或抽象类,而依赖倒置原则的出现,正是将这一传统的依赖倒置过来,使得具体类依赖于接口或抽象类。
现在假设有这么一需求,用户输入信息后点击提交,系统负责读取、校验和永久存储,下面是Android代码实现:
findViewById(R.id.tv_submit).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userInput = mEditText.getText().toString();
if(!TextUtils.isEmpty(userInput) && inputNews.check(userInput)){ //验证输入的数据是否符合业务逻辑
SQLiteDatabase db = new DbOpenHelper(ProviderActivity01.this).getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put("input", userInput);
db.insert("user", null, contentValues);
}else{
Toast.makeText(ProviderActivity01.this,"抱歉,你输入的数据不合法",Toast.LENGTH_SHORT).show();
}
}
});
一个普遍的现象,高层级模块依赖于底层级模块,比如上面代码中的UI层依赖业务层,业务层依赖数据层。那么如何用抽象来实现依赖倒置原则,解决上面高层模块依赖底层模块的现象呢?另一方面,我们也不想要一个简单的完整的类来完成所有的事情,这时就可以想到单一职责原则,将各个职能进行划分,来优化上面的代码.
数据层
public interface DataLayer {
void insert(String value);
}
/**实现类*/
public class DataLayerImpl implements DataLayer{
private Context mContext;
public DataLayerImpl(Context context){
mContext = context;
}
@Override
public void insert(String value){
SQLiteDatabase db = new DbOpenHelper(mContext).getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put("input",value);
db.insert("user",null,contentValues);
}
}
业务层
public interface BusinessLayer {
void check(String str);
}
/**实现类*/
public class BusinessLayerImpl implements BusinessLayer {
private DataLayer mDataLayer;
public BusinessLayerImpl(DataLayer mDataLayer) {
this.mDataLayer = mDataLayer;
}
@Override
public void check(String str){
//...业务逻辑校验,略
mDataLayer.insert(str);
}
}
UI层
public class ProviderActivity extends AppCompatActivity {
private EditText mEditText;
private DataLayer mDataLayer;
private BusinessLayer mBusinessLayer;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
initView();
findViewById(R.id.tv_submit).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userInput = mEditText.getText().toString();
if(!TextUtils.isEmpty(userInput)){
mDataLayer = new DataLayerImpl(ProviderActivity01.this);
mBusinessLayer = new BusinessLayerImpl(dataLayer);
mBusinessLayer.check(userInput);
}else{
Toast.makeText(ProviderActivity01.this,"抱歉,你输入的数据不合法",Toast.LENGTH_SHORT).show();
}
}
});
}
数据层和业务层的解耦,通过接口和构造注入解决;在面向对象的世界里,类与类之间可以有这么几种关系:
- 零耦合:表现在两个类之间没有任何耦合关系;
- 具体耦合:表现在一个类对另一个类的直接引用;
- 抽象耦合:表现在一个具体类和一个抽象类之间;
那上面的伪代码还是主要体现在了抽象耦合,通过对业务层的构造函数传入数据层的接口,这样久使得依赖关系存在了最大的灵活性。最后,我们高层级的模块都依赖于抽象了(接口)。更进一步,我们的抽象不依赖于细节,它们也依赖于抽象。
总结上面的伪代码,UI 层是依赖于业务逻辑层的接口的,业务逻辑层的接口依赖于数据层的接口.