当前疫情严重,居家不得外出,只好将以前写的代码拿出来重构一下。首先申明,没有什么技术含量,只是作业笔记,如有幸得高人指点,当然更好。
这是在android上实现的莫尔斯编码器app,程序的构造不甚理想,重构之前java行数为1115。实现原理请参照在安卓手机上实现莫尔斯编码器
首先考虑过度设计,当初读了一些模式设计的书,似懂非懂用在程序中,有些地方难免画蛇添足。
abstract class AbstractMorsePlayFactory {
//createMorsePlay的参数必须是IMorsePlay的实现类
public abstract <T extends IMorsePlay> T createMorsePlay(Class<T> c);
}
interface IMorsePlay<T> {
void setSpeed(T _speedFlag);
void playDa();
void playDi();
void playBlank();
}
class MorsePlayFactory extends AbstractMorsePlayFactory {
public <T extends IMorsePlay> T createMorsePlay(Class<T> c){
//定义一个生产对象MorseCode
IMorsePlay morsePlay = null;
try {
morsePlay = (T)Class.forName(c.getName()).newInstance();
} catch(Exception e) {
System.out.println("Create MorseCode error");
}
return (T)morsePlay;
}
}
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks{
//...
private IMorsePlay<Boolean> morsePlay; //播放类
//实例化播放工厂类
AbstractMorsePlayFactory morsePlayFactory = new MorsePlayFactory();
//获得播放类的实例
morsePlay = morsePlayFactory.createMorsePlay(MorsePlay.class);
//播放按钮的监听器
btnPlay.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
//设定播放速度
morsePlay.setSpeed(toggleBtnSpeed.isChecked());
//根据txtView的内容逐个翻译成音频信号
for (char chr : textViewMorse.getText().toString().toCharArray()){
switch (chr) {
case '.':
morsePlay.playDi();
break;
case '_':
morsePlay.playDa();
break;
default:
morsePlay.playBlank();
break;
}
}
}
});
}
class MorsePlay implements IMorsePlay<Boolean> {
//实现IMorsePlay
}
这里用到了工厂方法模式(Factory Method Pattern),工厂方法定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到它的子类。工厂方法具有良好的封装性和扩展性,适合团体编程、分工开发,但是增加了代码量和复杂度。这里只需要一个工厂类,它的父类AbstractMorsePlayFactory不是必须的,为了简化代码,可以使用静态工厂模式(Static Factory Method),删除AbstractMorsePlayFactory,把createMorsePlay方法设为静态方法,修改如下:
//class MorsePlayFactory extends AbstractMorsePlayFactory {
class MorsePlayFactory {
//public <T extends IMorsePlay> T createMorsePlay(Class<T> c){
static <T extends IMorsePlay> T createMorsePlay(){
//...
}
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks{
//...
private IMorsePlay<Boolean> morsePlay; //播放类
//实例化播放工厂类
//AbstractMorsePlayFactory morsePlayFactory = new MorsePlayFactory();
//获得播放类的实例
//morsePlay = morsePlayFactory.createMorsePlay(MorsePlay.class);
morsePlay = MorsePlayFactory.createMorsePlay(MorsePlay.class);
//...
}
下面这段代码有不少重复处理,应该有精简的余地。
class ArrayMapForCodingFactory {
private static String[] charArray; //字符数组 包含字母、数字、符号
private static String[] morseCodeArray; //字符数组对应的莫尔斯码数组
private static String[] figuresArray; //数字数组
private static String[] morseLongCodeArray; //数字数组对应的莫尔斯长码数组
private static String[] morseShortCodeArray; //数字数组对应的莫尔斯短码数组
//构造函数
ArrayMapForCodingFactory(){
//获取字符数组和莫尔斯码数组
Context context = MyApplication.getContextObject();
charArray = context.getResources().getStringArray(R.array.characters);
morseCodeArray = context.getResources().getStringArray(R.array.morse_code);
figuresArray = context.getResources().getStringArray(R.array.figures);
morseLongCodeArray = context.getResources().getStringArray(R.array.morse_long_code);
morseShortCodeArray = context.getResources().getStringArray(R.array.morse_short_code);
}
//生产标准编码类的工厂方法
IArrayMapForCoding createArrayMapCodingNormal(){
//定义一个工厂生产的ArrayMapForCoding类
IArrayMapForCoding arrayMapForCoding = null;
try {
//生产ArrayMapForCoding类
arrayMapForCoding = new ArrayMapForCoding(charArray, morseCodeArray);
} catch (Exception e){
System.out.println("ArrayMapCodingNormal类生成错误!");
}
return arrayMapForCoding;
}
//生产长码电报编码类的工厂方法
IArrayMapForCoding createArrayMapCodingLong(){
//定义一个工厂生产的ArrayMapForCoding类
IArrayMapForCoding arrayMapForCoding = null;
try {
//生产ArrayMapForCoding类
arrayMapForCoding = new ArrayMapForCoding(figuresArray, morseLongCodeArray);
} catch (Exception e){
System.out.println("ArrayMapCodingLong类生成错误!");
}
return arrayMapForCoding;
}
//生产短码电报编码类的工厂方法
IArrayMapForCoding createArrayMapCodingShort(){
//定义一个工厂生产的ArrayMapForCoding类
IArrayMapForCoding arrayMapForCoding = null;
try {
//生产ArrayMapForCoding类
arrayMapForCoding = new ArrayMapForCoding(figuresArray, morseShortCodeArray);
} catch (Exception e){
System.out.println("ArrayMapCodingShort类生成错误!");
}
return arrayMapForCoding;
}
}
删除不必要的异常处理,直接调用ArrayMapForCoding的构造函数,获得摩尔斯编码类。
class ArrayMapForCodingFactory {
//...
//生产标准编码类的工厂方法
IArrayMapForCoding createArrayMapCodingNormal(){
return new ArrayMapForCoding(charArray, morseCodeArray);
}
//生产长码电报编码类的工厂方法
IArrayMapForCoding createArrayMapCodingLong(){
return new ArrayMapForCoding(figuresArray, morseLongCodeArray);
}
//生产短码电报编码类的工厂方法
IArrayMapForCoding createArrayMapCodingShort(){
return new ArrayMapForCoding(figuresArray, morseShortCodeArray);
}
很显然,下面这段代码也可以精简。
class AllCapTransformationMethod extends ReplacementTransformationMethod {
@Override
protected char[] getOriginal() {
char[] aa;
aa = new char[]{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
return aa;
}
@Override
protected char[] getReplacement() {
char[] cc;
cc = new char[]{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
return cc;
}
}
修改后是这样的。
class AllCapTransformationMethod extends ReplacementTransformationMethod {
@Override
protected char[] getOriginal() {
return new char[]{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
}
@Override
protected char[] getReplacement() {
return new char[]{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
}
}
下面这个类也需要提炼。
class MorsePlay implements IMorsePlay<Boolean> {
//...
public MorsePlay() {
// 设置最多可容纳4个音频流,音频的品质为5
// load方法加载指定音频文件,并返回所加载的音频ID。
Context context = MyApplication.getContextObject();
streamIdSlowDi = soundPool.load(context, R.raw.morse_di_070ms_600hz , 1);
streamIdSlowDa = soundPool.load(context, R.raw.morse_da_210ms_600hz , 1);
streamIdFastDi = soundPool.load(context, R.raw.morse_di_050ms_600hz , 1);
streamIdFastDa = soundPool.load(context, R.raw.morse_da_150ms_600hz , 1);
}
public void finalize(){
soundPool.release();
try {
super.finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
public void setSpeed(Boolean _speedFlag){
speedFlag = _speedFlag;
}
public void playDa(){
if (speedFlag){
soundPool.play(streamIdFastDa,1.0f, 1.0f, 0, 0, 1.0f);
delay(delayFastDa + delayFastDi);
}
else{
soundPool.play(streamIdSlowDa,1.0f, 1.0f, 0, 0, 1.0f);
delay(delaySlowDa + delaySlowDi);
}
}
public void playDi(){
if (speedFlag){
soundPool.play(streamIdFastDi,1.0f, 1.0f, 0, 0, 1.0f);
delay(delayFastDi + delayFastDi);
}
else{
soundPool.play(streamIdSlowDi,1.0f, 1.0f, 0, 0, 1.0f);
delay(delaySlowDi + delaySlowDi);
}
}
public void playBlank(){
if (speedFlag){
delay(delayFastDa + delayFastDi);
}
else{
delay(delaySlowDa + delaySlowDi);
}
}
private void delay(long _ms){
try {
Thread.sleep(_ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
提炼后精简代码。
class MorsePlay implements IMorsePlay<Boolean> {
//...
public MorsePlay() {
// 设置最多可容纳4个音频流,音频的品质为5
// load方法加载指定音频文件,并返回所加载的音频ID。
Context context = MyApplication.getContextObject();
streamIdSlowDi = soundPool.load(context, R.raw.morse_di_070ms_600hz , 1);
streamIdSlowDa = soundPool.load(context, R.raw.morse_da_210ms_600hz , 1);
streamIdFastDi = soundPool.load(context, R.raw.morse_di_050ms_600hz , 1);
streamIdFastDa = soundPool.load(context, R.raw.morse_da_150ms_600hz , 1);
}
public void finalize(){
soundPool.release();
try {
super.finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
public void setSpeed(Boolean _isFast){
isFast = _isFast;
}
public void playDa(){
soundPool.play(isFast ? streamIdFastDa : streamIdSlowDa,1.0f, 1.0f, 0, 0, 1.0f);
delay(isFast ? delayFastDa + delayFastDi : delaySlowDa + delaySlowDi);
}
public void playDi(){
soundPool.play(isFast ? streamIdFastDi : streamIdSlowDi,1.0f, 1.0f, 0, 0, 1.0f);
delay(isFast ? delayFastDi + delayFastDi : delaySlowDi + delaySlowDi);
}
public void playBlank(){
delay(isFast ? delayFastDa + delayFastDi : delaySlowDa + delaySlowDi);
}
private void delay(long _ms){
try {
Thread.sleep(_ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
下面这两个类使用String进行字符串拼接,显然应该改正,否则无端消耗内存空间。另外使用了MainActivity中的类,破坏了封装性。
class NormalCoder implements ICoder {
//获得莫尔斯码
public String code(String _text) {
String returnValue = "";
//把字母、数字和空格翻译成莫尔斯码 数字用长码表示
for (char chr : _text.toUpperCase().toCharArray()){
if (chr == ' '){
returnValue += " ";
}
else {
returnValue += MainActivity.arrayMapForCodingNormal.GetMorseCode(chr);
}
returnValue += " ";
}
return returnValue;
}
}
class TelegramCoder implements ICoder {
//数字长码标识
private final boolean IsLongCode;
//构造函数 指定长短码
TelegramCoder(boolean _isLongCode){ IsLongCode = _isLongCode; }
//获得莫尔斯码
//把数字和空格翻译成莫尔斯码 数字表示取决于长码标识IsLongCode
public String code(String _text){
String returnValue = "";
for (char chr : _text.toUpperCase().toCharArray()){
if (chr == ' '){
returnValue += " ";
}
else {
if (IsLongCode){
returnValue += MainActivity.arrayMapForCodingLong.GetMorseCode(chr);
}
else {
returnValue += MainActivity.arrayMapForCodingShort.GetMorseCode(chr);
}
}
returnValue += " ";
}
return returnValue;
}
}
应该使用StringBuilder,并且用构造函数从外部注入arrayMapForCodingNormal类,两个类合并为一个,合并后新类代码如下。
public class MorseCoder implements ICoder {
private IArrayMapForCoding arrayMapForCoding;
//构造函数,注入ArrayMapForCoding
MorseCoder(IArrayMapForCoding _arrayMapForCoding) {
arrayMapForCoding = _arrayMapForCoding;
}
//获得莫尔斯码
@Override
public String code(String _text) {
StringBuilder stringBuilder = new StringBuilder();
//把字母、数字和空格翻译成莫尔斯码
for (char chr : _text.toUpperCase().toCharArray()) {
if (chr == ' ') {
stringBuilder.append(" ");
} else {
stringBuilder.append(arrayMapForCoding.GetMorseCode(chr));
}
stringBuilder.append(" ");
}
return stringBuilder.toString();
}
}
修改他们的使用者MainActivity,删除原有的两个类。
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks{
//...
protected void onCreate(Bundle savedInstanceState) {
//...
//莫尔斯码生成按钮的监听器
btnCode.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
boolean isLongCode = toggleBtnLong.isChecked(); //指定长短码
CoderContext coderContext; //使用策略模式,封装角色 简化顶层逻辑
//根据电文格式和长短码,实例化编码类
if (toggleBtnFormat.isChecked()){
//coderContext = new CoderContext(new TelegramCoder(isLongCode));
coderContext = new CoderContext(new MorseCoder(isLongCode ? arrayMapForCodingLong : arrayMapForCodingShort));
}
else {
//coderContext = new CoderContext(new NormalCoder());
coderContext = new CoderContext(new MorseCoder(arrayMapForCodingNormal));
}
textViewMorse.setText(coderContext.code(editText.getText().toString()));
}
});
}
}
下面的类用于在子类中直接获取资源,把context定义为静态变量,存在内存泄漏风险,导致Activity无法正常销毁。
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate() {
super.onCreate();
//获取Context
context = getApplicationContext();
}
//返回
public static Context getContextObject(){
return context;
}
}
修改方法是删除MyApplication类,把android资源直接传给需要使用资源的子类。
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks{
//...
protected void onCreate(Bundle savedInstanceState) {
//...
//实例化编码工厂类
//ArrayMapForCodingFactory arrayMapForCodingFactory = new ArrayMapForCodingFactory();
ArrayMapForCodingFactory arrayMapForCodingFactory = new ArrayMapForCodingFactory(res);
//获得播放类的实例
//morsePlay = MorsePlayFactory.createMorsePlay();
morsePlay = MorsePlayFactory.createMorsePlay(this);
//获得制作音频文件类的实例
//makeMorseAudioFile = new MakeMorseAudioFile();
makeMorseAudioFile = new MakeMorseAudioFile(res);
}
}
子类的修改不再赘述,需要说明的是MorsePlay类的实例是在工厂方法中用类反射取得的,原来是无参类反射,修改为有参类反射,如下代码。
class MorsePlayFactory {
//static <T extends IMorsePlay> T createMorsePlay(){
static <T extends IMorsePlay> T createMorsePlay(Context _context){
//定义一个生产对象MorseCode
IMorsePlay morsePlay = null;
try {
//morsePlay = (T)Class.forName(MorsePlay.class.getName()).newInstance();
morsePlay = (T)Class.forName(MorsePlay.class.getName()).getDeclaredConstructor(Context.class).newInstance(_context);
} catch(Exception e) {
System.out.println("Create MorsePlay error");
}
return (T)morsePlay;
}
}
经过测试,代码正确运行,至此java代码行数为982,减少了133行。修改后的代码放在老地方。