(设计原则1)针对接口编程,不针对实现编程

最近看两个安卓程序都用到了接口(interface)将主要方法封装起来,然后利用多态确定调用的方法的具体实现。

接口——抽象方法的集合

从java教程上面都能看到接口一下特点:

  1. 接口以及抽象类不能直接被实例化,即不同通过new创建实例对象。
  2. 接口要有子类,一个类可以实现(implements)多个接口。
  3. 子类必须覆写(@override)接口中所有抽象方法。
  4. 接口中可以包含变量,但都是static final,所以必须初始化,子类不能对接口中的变量重新赋值。
  5. 接口对象可以通过子类可以向上转型进行实例化。(实现多态)

对于初学者来说,知道了以上特点并不能很好使用接口,因为往往被第1个特点唬到,而没有意识到第5个特点的妙处。

下面利用Java设计原则——针对接口编程,不针对实现编程,说明接口的用途。

程序代码是Google 利用安卓7.0版本增加的获取GNSS原始观测数据的相关类写的一个app,代码稍作修改。

App开源程序见:GNSSLogger

public interface GnssListener {

    /**@see LocationListener#onProviderEnabled(String) */
    void onProviderEnabled(String provider);
    /**@see LocationListener#onProviderDisabled(String) */
    void onproviderDisabled(String provider);
    /**@see LocationListener#onLocationChanged(Location) */
    void onLocationChanged(Location location);
    /**@see LocationListener#onStatusChanged(String, int, Bundle) */
    void onLocationStatusChanged(String provider, int status, Bundle extras);

    /**@see GnssMeasurementsEvent.Callback#onGnssMeasurementsReceived(GnssMeasurementsEvent)*/
    void onGnssMeasurementsReceived(GnssMeasurementsEvent event);
    /**@see GnssMeasurementsEvent.Callback#onStatusChanged(int) */
    void onGnssMeasurementsStatusChanged(int status);

    /**@see GnssNavigationMessage.Callback#onGnssNavigationMessageReceived(GnssNavigationMessage) */
    void onGnssNavigationMessageReceived(GnssNavigationMessage event);
    /**@see GnssNavigationMessage.Callback#onStatusChanged(int) */
    void onGnssNavigationMessageStatusChanged(int status);

    /**@see GnssStatus.Callback#onSatelliteStatusChanged(GnssStatus) */
    void onGnssStatusChanged(GnssStatus gnssStatus);
    /**called when the listener is registered to listen to GNSS event*/
    void onListenerRegistered(String listener,boolean result);
    /**@see android.location.OnNmeaMessageListener#onNmeaMessage(String, long) */
    void onNmeaMessage(long l,String s);
    void onTTFFReceived(long l);

}

 上面的接口将接口LocationListener、抽象内部类 GnssMeasurementsEvent.Callback、GnssNavigationMessage.Callback、GnssStatus.Callback 的主要方法都囊括其中,这些方法都是用来获取GNSS相关数据的。

第二个关键类是GnssContainer,其构造函数需要传入GnssListener的对象,所以需要有子类实现GnssListener接口,子类为FileLogger,但是从代码可以看出它并没有对接口中的方法添加其他代码,这种继承(实现也是一种继承)保持了FileLogger与GnssListener具有相同类型。

public class GnssContainer {
    public static final String TAG="GnssLogger";

    private boolean firstTime=true;
    private boolean mLogLocations = true;
    private boolean mLogMeasurements = true;

    private final List<GnssListener> mLoggers;
    private LocationManager mLocationManager;

    public GnssContainer(Context context,GnssListener...loggers){
        this.mLoggers=Arrays.asList(loggers);
        mLocationManager=(LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
    }

    private final GnssMeasurementsEvent.Callback gnssMeasurementsEventListener= 
new GnssMeasurementsEvent.Callback() {
        @Override
        public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
            if (mLogMeasurements) {
                for (GnssListener logger : mLoggers) {
                    logger.onGnssMeasurementsReceived(event);
                }
            }
        }
        @Override
        public void onStatusChanged(int status) {
            if (mLogMeasurements) {
                for (GnssListener logger : mLoggers) {
                    logger.onGnssMeasurementsStatusChanged(status);
                }
            }
        }
    };

    public void registerMeasurements() {
        try{
            mLocationManager.registerGnssMeasurementsCallback(gnssMeasurementsEventListener);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }

    public void unregisterMeasurements() {
        mLocationManager.unregisterGnssMeasurementsCallback(gnssMeasurementsEventListener);
    }
}

FileLogger.java

public class FileLogger implements GnssListener {
    private final Context mContext;
    private final Object mFileLock = new Object();
    private BufferedWriter mFileWriter;
    private File mFile;

    private static final String TAG="FileLogger"
    private static final String FILE_PREFIX = "gnss_log";
    private static final String COMMENT_START = "# ";
    private static final char RECORD_DELIMITER = ',';
    private static final String VERSION_TAG = "Version: ";


    public FileLogger(Context context){
        this.mContext=context;
    }


    public void startNewLog(){
        synchronized (mFileLock){
            File baseDirectory;
            String state=Environment.getExternalStorageState();
            if(Environment.MEDIA_MOUNTED.equals(state)){
                baseDirectory=new File(Environment.getExternalStorageDirectory(),FILE_PREFIX);
                baseDirectory.mkdirs();
            }else if(Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)){
                Log.e(TAG,"只读");
                return;
            }else {
                Log.e(TAG,"不能读取存储");
                return;
            }

            SimpleDateFormat format=new SimpleDateFormat("yyy_MM_dd_HH_mm_ss");
            Date now=new Date();
            String fileName=String.format("%s_%s.txt",FILE_PREFIX,format.format(now));
            File currentFile=new File(baseDirectory,fileName);
            String currentFilePath=currentFile.getAbsolutePath();
            BufferedWriter currentFileWriter;
            try{
                currentFileWriter=new BufferedWriter(new FileWriter(currentFile));
            }catch (IOException e){
                Log.e(TAG,"不能打开文件");
                return;
            }

            try{
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.write("Header Description:");
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.write(VERSION_TAG);
                String manufacturer = Build.MANUFACTURER;
                String model = Build.MODEL;
                String fileVersion =
                        mContext.getString(R.string.app_version)
                                + " Platform: "
                                + Build.VERSION.RELEASE
                                + " "
                                + "Manufacturer: "
                                + manufacturer
                                + " "
                                + "Model: "
                                + model;
                currentFileWriter.write(fileVersion);
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.write(
                        "Raw,ElapsedRealtimeMillis,TimeNanos,LeapSecond,TimeUncertaintyNanos,FullBiasNanos,"
                                + "BiasNanos,BiasUncertaintyNanos,DriftNanosPerSecond,DriftUncertaintyNanosPerSecond,"
                                + "HardwareClockDiscontinuityCount,Svid,TimeOffsetNanos,State,ReceivedSvTimeNanos,"
                                + "ReceivedSvTimeUncertaintyNanos,Cn0DbHz,PseudorangeRateMetersPerSecond,"
                                + "PseudorangeRateUncertaintyMetersPerSecond,"
                                + "AccumulatedDeltaRangeState,AccumulatedDeltaRangeMeters,"
                                + "AccumulatedDeltaRangeUncertaintyMeters,CarrierFrequencyHz,CarrierCycles,"
                                + "CarrierPhase,CarrierPhaseUncertainty,MultipathIndicator,SnrInDb,"
                                + "ConstellationType,AgcDb,CarrierFrequencyHz");
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.write(
                        "Fix,Provider,Latitude,Longitude,Altitude,Speed,Accuracy,(UTC)TimeInMs");
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.write("Nav,Svid,Type,Status,MessageId,Sub-messageId,Data(Bytes)");
                currentFileWriter.newLine();
                currentFileWriter.write(COMMENT_START);
                currentFileWriter.newLine();
            }catch (IOException e){
                Log.e(TAG,"不能初始化文件");
                return;
            }

            if(mFileWriter!=null){
                try{
                    mFileWriter.close();
                }catch (IOException e){
                    return;
                }
            }

            mFile=currentFile;
            mFileWriter=currentFileWriter;
            Toast.makeText(mContext,"File opened:"+currentFilePath,Toast.LENGTH_SHORT).show();

        }
    }
    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onproviderDisabled(String provider) {

    }

    @Override
    public void onLocationChanged(Location location) {

    }

    @Override
    public void onLocationStatusChanged(String provider, int status, Bundle extras) {

    }

    @Override
    public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
        synchronized (mFileLock) {
            if (mFileWriter == null) {
                return;
            }
            GnssClock gnssClock = event.getClock();
            for (GnssMeasurement measurement : event.getMeasurements()) {

                try {

                    writeGnssMeasurementToFile(gnssClock, measurement);

                } catch (IOException e) {

                    e.printStackTrace();
                   // logException(ERROR_WRITING_FILE, e);

                }

            }

        }
    }

    @Override
    public void onGnssMeasurementsStatusChanged(int status) {

    }

    @Override
    public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {

    }

    @Override
    public void onGnssNavigationMessageStatusChanged(int status) {

    }

    @Override
    public void onGnssStatusChanged(GnssStatus gnssStatus) {

    }

    @Override
    public void onListenerRegistered(String listener, boolean result) {

    }

    @Override
    public void onNmeaMessage(long l, String s) {

    }

    @Override
    public void onTTFFReceived(long l) {

    }

    private void writeGnssMeasurementToFile(GnssClock clock, GnssMeasurement measurement) throws IOException {
        String clockStream = String.format("Raw,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s",
                SystemClock.elapsedRealtime(),
                clock.getTimeNanos(),
                clock.hasLeapSecond() ? clock.getLeapSecond() : "",
                clock.hasTimeUncertaintyNanos() ? clock.getTimeUncertaintyNanos() : "",
                clock.getFullBiasNanos(),
                clock.hasBiasNanos() ? clock.getBiasNanos() : "",
                clock.hasBiasUncertaintyNanos() ? clock.getBiasUncertaintyNanos() : "",
                clock.hasDriftNanosPerSecond() ? clock.getDriftNanosPerSecond() : "",
                clock.hasDriftUncertaintyNanosPerSecond() ? clock.getDriftUncertaintyNanosPerSecond(): "",
                clock.getHardwareClockDiscontinuityCount() + ",");
        mFileWriter.write(clockStream);
        String measurementStream = String.format(
                "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s",
                measurement.getSvid(),
                measurement.getTimeOffsetNanos(),
                measurement.getState(),
                measurement.getReceivedSvTimeNanos(),
                measurement.getReceivedSvTimeUncertaintyNanos(),
                measurement.getCn0DbHz(),
                measurement.getPseudorangeRateMetersPerSecond(),
                measurement.getPseudorangeRateUncertaintyMetersPerSecond(),
                measurement.getAccumulatedDeltaRangeState(),
                measurement.getAccumulatedDeltaRangeMeters(),
                measurement.getAccumulatedDeltaRangeUncertaintyMeters(),
                measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : "",
                measurement.hasCarrierCycles() ? measurement.getCarrierCycles() : "",
                measurement.hasCarrierPhase() ? measurement.getCarrierPhase() : "",
                measurement.hasCarrierPhaseUncertainty() ? measurement.getCarrierPhaseUncertainty(): "",
                measurement.getMultipathIndicator(),
                measurement.hasSnrInDb() ? measurement.getSnrInDb() : "",
                measurement.getConstellationType(),
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                && measurement.hasAutomaticGainControlLevelDb() ?
                        measurement.getAutomaticGainControlLevelDb() : "",
                measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : "");
        mFileWriter.write(measurementStream);
        mFileWriter.newLine();
    }

    public void end() {
        if (mFileWriter != null) {
            try {
                mFileWriter.flush();
                mFileWriter.close();
                mFileWriter = null;
            } catch (IOException e) {
                return;
            }
        }
    }
}
MainActivity.java部分代码
 @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_bt:
                fileLogger=new FileLogger(this);
                fileLogger.startNewLog();
                gnssContainer=new GnssContainer(this,fileLogger);
                gnssContainer.registerMeasurements();
                break;
            case R.id.end_bt:
                fileLogger.end();
                gnssContainer.unregisterMeasurements();
        }
    }

在MainActivity中,FileLogger类对象可以作为GnssContainer构造函数参数,这也就相当于告诉GnssContainer可以使用接口中的方法了。

上面的例子可能不太能说明标题,推荐阅读《HeadFirst 设计模式》,这本书很生动形象地介绍了多种设计模式和设计原则,对于理解源码十分有帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值