英文:
The best way to use one class, with only one changed object inside - Kotlin/Android/Java
问题
我已经在安卓上创建了一个简单的游戏。现在我想要添加难度级别,我在考虑采取什么方法。我的意思是,我只需要改变类中的一个对象(CountDownTimer)来改变难度,起初我想为每个级别创建3个类,但直觉告诉我那会非常冗余。是创建一个类然后从它继承?还是以某种方式创建一个带有参数的对象?你能给予一些建议吗?
package com.whayway.beerrandom
import ...
// 其他部分保持不变
class GameFragment : Fragment() {
// 其他部分保持不变
// 在这里添加一个变量来表示难度级别
private var difficultyLevel: Int = 1 // 默认难度为1
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 其他部分保持不变
hideImages()
// 使用 difficultyLevel 来设置不同的倒计时和难度
val countdownInterval = when (difficultyLevel) {
1 -> 15000 // 难度级别1的倒计时时间
2 -> 10000 // 难度级别2的倒计时时间
3 -> 5000 // 难度级别3的倒计时时间
else -> 15000
}
object : CountDownTimer(countdownInterval, 1000) {
override fun onTick(p0: Long) {
// 其他部分保持不变
}
override fun onFinish() {
// 其他部分保持不变
}
}.start()
// 其他部分保持不变
return binding.root
}
// 其他部分保持不变
// 添加一个函数来设置难度级别
private fun setDifficultyLevel(level: Int) {
difficultyLevel = level
// 在这里可以根据难度级别进行其他设置
}
}
你可以通过调用 setDifficultyLevel()
函数来设置不同的难度级别,然后在 onCreateView()
函数中根据难度级别来设置不同的倒计时时间和其他难度相关的设置。这样,你只需要改变难度级别,而不需要创建多个类来管理不同的难度。
英文:
I've created simple game on android. Now I want to add difficulty levels and I'm wondering what approach to take. I mean, I need to change only one object (CountDownTimer) inside class to change that difficulty, and first I thought that I will make just 3 classes for every level, but my intuition tells me that will be big overkill. Do a class and inherit from it? Or maybe create in some way an object with a parameter? Can you suggest something?
package com.whayway.beerrandom
import android.os.Bundle
import android.os.CountDownTimer
import android.os.Handler
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.navigation.findNavController
import com.whayway.beerrandom.databinding.FragmentGameBinding
import kotlinx.android.synthetic.main.fragment_game.*
import java.util.*
const val KEY_SCORE = "score_key"
class GameFragment : Fragment() {
var handler: Handler = Handler()
var score: Int = 0
var runnable: Runnable = Runnable { }
var imageArray = ArrayList<ImageView>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<FragmentGameBinding>(
inflater, R.layout.fragment_game, container, false
)
score = 0
if(savedInstanceState != null){
score = savedInstanceState.getInt(KEY_SCORE, 0)
}
hideImages()
object : CountDownTimer(15000, 1000) {
override fun onTick(p0: Long) {
btn_ok.visibility = View.INVISIBLE
timeText.text = "Time: " + (p0 / 1000)
}
override fun onFinish() {
timeText.text = "Time out"
handler.removeCallbacks(runnable)
for (image in imageArray) {
image.visibility = View.INVISIBLE
btn_ok.visibility = View.VISIBLE
}
}
}.start()
score = 0
setHasOptionsMenu(true)
/* val args = .fromBundle(arguments!!)
Toast.makeText(context, "NumCorrect: ${args.numCorrect}, NumQuestions: ${args.numQuestions}", Toast.LENGTH_LONG).show()*/
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
imageArray = arrayListOf(
imageView8,
imageView7,
imageView6,
imageView5,
imageView4,
imageView3,
imageViewPawel,
imageView111
)
scoreText.setOnClickListener {}
imageView8.setOnClickListener { increaseScore() }
imageView7.setOnClickListener { increaseScore() }
imageView8.setOnClickListener{increaseScore() }
imageView6.setOnClickListener { increaseScore() }
imageView5.setOnClickListener { increaseScore() }
imageView4.setOnClickListener { increaseScore()
setImage() }
imageView3.setOnClickListener { decreaseScore()
setImage() }
imageViewPawel.setOnClickListener { increaseScorePawel()
setImage()}
imageView111.setOnClickListener { increaseScore()
setImage()}
btn_ok.setOnClickListener { view: View ->
view.findNavController().navigate(
GameFragmentDirections
.actionGameFragmentToResultFragment(score)
)
}
super.onViewCreated(view, savedInstanceState)
}
private fun setImage(){
var drawableArray = ArrayList<Int>()
drawableArray = arrayListOf(
R.drawable.pawel,
R.drawable.leszek,
R.drawable.lech_free)
val random = Random()
val index = random.nextInt(3 - 1)
val index2 = random.nextInt(8 - 1)
imageArray[index2].setImageResource(drawableArray[index])
}
private fun hideImages() {
runnable = Runnable {
for (image in imageArray) {
image.visibility = View.INVISIBLE
}
val random = Random()
val index = random.nextInt(8 - 1)
imageArray[index].visibility = View.VISIBLE
handler.postDelayed(runnable, 500)
}
handler.post(runnable)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(KEY_SCORE, score)
}
private fun increaseScore() {
score++
scoreText.text = "Score:" + score
}
private fun increaseScorePawel() {
score += 5
scoreText.text = "Score:" + score
}
private fun decreaseScore() {
score -= 10
scoreText.text = "Score:" + score
}
}
答案1
得分: 3
以下是翻译的内容:
没有一个单一的最佳答案来回答你的问题,但对我来说最合理的解决方案是创建一个 CountDownTimer 的实现。
我不确定你想在计时器内部进行什么改变,但你应该能够通过将一个值传递给构造函数来处理大部分事情:
class GameTimer(interval: Long) : CountDownTimer(15000, interval) {
...
}
或者使用回调函数(自定义行为):
class GameTimer(onTimeOut: () -> Unit) : CountDownTimer(15000, 1000) {
...
override fun onFinish() { onTimeOut() }
}
我认为引入工厂设计模式是个好主意。由于这对你可能听起来没有意义,让我举个例子给你看:
// 也可以是一个带有构造参数的类,这完全取决于你的用例
object GameTimerFactory {
fun easyGameTimer() = GameTimer(1000)
fun mediumGameTimer() = GameTimer(800)
fun hardGameTimer() = GameTimer(650)
}
根据你从更高层次的组件调用它的方式,你可能想要使用枚举:
enum class Difficulty { EASY, MEDIUM, HARD }
object GameTimerFactory {
fun gameTimerFor(difficulty: Difficulty): GameTimer = when (difficulty) {
...
}
}
还有一个小提示:明智的做法是将游戏逻辑从 Android 逻辑中抽离出来,以符合关注点分离原则(SoC 原则)。简而言之,在每个程序中都有一些层次,例如在你的程序中,我们很容易看到一个特定于 Android 的图层,用于显示游戏,以及包含游戏规则的图层。这将在你希望将游戏移植到其他平台(例如桌面)时非常有用,如果你想对游戏逻辑进行单元测试,这几乎是必需的。有很多很好的解释:
英文:
There's no single best answer to your question but the solution that makes the most sense to me is to create an implementation of CountDownTimer.
I'm not sure what it is that you want to change inside of the counter, but you should be able to deal with most of the things by passing a value to the constructor:
class GameTimer(interval: Long) : CountDownTimer(15000, interval) {
...
}
or a callback (custom behavior):
class GameTimer(onTimeOut: () -> Unit) : CountDownTimer(15000, 1000) {
...
override fun onFinish() { onTimeOut() }
}
I think it would be a good idea to introduce factory design pattern. Since it may sound meaningless to you, let me show you an example:
// could be also a class with constructor parameters, it all depends on your use case
object GameTimerFactory {
fun easyGameTimer() = GameTimer(1000)
fun mediumGameTimer() = GameTimer(800)
fun hardGameTimer() = GameTimer(650)
}
depending on how you will call it from higher level component you may want to use an enum:
enum class Difficulty { EASY, MEDIUM, HARD }
object GameTimerFactory {
fun gameTimerFor(difficulty: Difficulty): GameTimer = when (difficulty) {
...
}
}
Also, a tip: it would be wise to extract game logic from android logic in order to conform SoC principle. tl;dr: in every program there are some layers, for example in yours we can easily see an android-specific layer that displays the game and a layer that contains game rules. It will be useful in case you want to port the game to other platforms (e.g. desktop) and pretty much necessary if you want to unit test the game logic. There are lots of nice explanations out there:
答案2
得分: 1
你正在使用以下方法创建CountDownTimer:
> CountDownTimer(long millisInFuture, long countDownInterval)
因此,在你的类中添加两个属性:millisInFuture 和 countDownInterval。
然后,创建一个构造函数来设置它们。
当你实例化你的类时,使用这个构造函数并传入你想要的CountDownTimer的值。
英文:
You are creating the CountDownTimer using this method:
> CountDownTimer(long millisInFuture, long countDownInterval)
So, just add two properties in your class: millisInFuture and countDownInterval.
Then, create a constructor to set them.
When you instantiate your class, use the constructor and pass the value you want for your CountDownTimer.
答案3
得分: 1
我的建议是不要重复编写代码,为您的目的创建3个片段(Fragment)类。有一个流行的编程术语 - DRY(不要重复自己的缩写)。
据我理解 - 您只需要在CountDownTimer对象中进行更改。因此,您可以在其他文件中编写3个具有名称的计时器对象。如文档所述:对象。根据用户的选择使用它们。
英文:
My advice is to do not repeat code to make 3 Fragment classes for your purpose. There is a popular programming term - DRY (acronym to Do Not Repeat Yourself).
As I understand - you need changes only in CountDownTimer object. So, you can write 3 of them in other files with names. As it is described in docs: objects. Use them depend on user's choice.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论